@@ -138,7 +138,7 @@ make_cp_async_gmem_tiled_copy() {
|
||||
|
||||
if constexpr (cutlass::gemm::detail::is_k_major<StrideType>()) {
|
||||
// K major thread layout for K major gmem
|
||||
constexpr int threads_major = TileSizeK / Alignment;
|
||||
constexpr int threads_major = (ThreadCount >= TileSizeK / Alignment) ? (TileSizeK / Alignment) : ThreadCount;
|
||||
constexpr int threads_minor = ThreadCount / threads_major;
|
||||
static_assert(threads_major > 0);
|
||||
static_assert(ThreadCount % threads_major == 0);
|
||||
@@ -151,7 +151,7 @@ make_cp_async_gmem_tiled_copy() {
|
||||
}
|
||||
else if constexpr (cutlass::gemm::detail::is_mn_major<StrideType>()) {
|
||||
// MN major thread layout for MN major gmem
|
||||
constexpr int threads_major = TileSizeMN / Alignment;
|
||||
constexpr int threads_major = (ThreadCount >= TileSizeMN / Alignment) ? (TileSizeMN / Alignment) : ThreadCount;
|
||||
constexpr int threads_minor = ThreadCount / threads_major;
|
||||
static_assert(threads_major > 0);
|
||||
static_assert(ThreadCount % threads_major == 0);
|
||||
|
||||
@@ -31,62 +31,11 @@
|
||||
#pragma once
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#include "cutlass/gemm/collective/collective_mma_decl.hpp"
|
||||
#include "cutlass/gemm/collective/collective_mma.hpp"
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Used to specify stage counts or dispatch to automatic computation of stage count
|
||||
template<int num_stages>
|
||||
struct StageCount {
|
||||
static constexpr int value = num_stages;
|
||||
|
||||
StageCount() = default;
|
||||
explicit StageCount(cute::Int<num_stages>) {}
|
||||
};
|
||||
|
||||
template<int carveout_bytes>
|
||||
struct StageCountAutoCarveout {
|
||||
static constexpr int bytes = carveout_bytes;
|
||||
|
||||
StageCountAutoCarveout() = default;
|
||||
explicit StageCountAutoCarveout(cute::Int<carveout_bytes>) {}
|
||||
};
|
||||
|
||||
using StageCountAuto = StageCountAutoCarveout<0>;
|
||||
|
||||
// Used to automatically let the builder pick the kernel schedule.
|
||||
// Can be overridden with kernel schedule tags in cutlass/gemm/dispatch_policy.hpp
|
||||
struct KernelScheduleAuto {};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
class ArchTag,
|
||||
class OpClass,
|
||||
class ElementA,
|
||||
class GmemLayoutA,
|
||||
int AlignmentA,
|
||||
class ElementB,
|
||||
class GmemLayoutB,
|
||||
int AlignmentB,
|
||||
class ElementAccumulator,
|
||||
class TileShape_MNK,
|
||||
class ClusterShape_MNK,
|
||||
class StageCountType,
|
||||
class KernelScheduleType,
|
||||
class Enable = void
|
||||
>
|
||||
struct CollectiveBuilder {
|
||||
static_assert(sizeof(ElementA) == 0, "Could not build a collective for given parameters.");
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::collective
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cutlass/gemm/collective/collective_builder_decl.hpp"
|
||||
#include "cutlass/gemm/collective/builders/sm90_gmma_builder.inl"
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 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/numeric/integral_constant.hpp>
|
||||
#include <cutlass/detail/dependent_false.hpp>
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Used to specify stage counts or dispatch to automatic computation of stage count
|
||||
template<int num_stages>
|
||||
struct StageCount {
|
||||
static constexpr int value = num_stages;
|
||||
|
||||
StageCount() = default;
|
||||
explicit StageCount(cute::Int<num_stages>) {}
|
||||
};
|
||||
|
||||
template<int carveout_bytes>
|
||||
struct StageCountAutoCarveout {
|
||||
static constexpr int bytes = carveout_bytes;
|
||||
|
||||
StageCountAutoCarveout() = default;
|
||||
explicit StageCountAutoCarveout(cute::Int<carveout_bytes>) {}
|
||||
};
|
||||
|
||||
using StageCountAuto = StageCountAutoCarveout<0>;
|
||||
|
||||
// Used to automatically let the builder pick the kernel schedule.
|
||||
// Can be overridden with kernel schedule tags in cutlass/gemm/dispatch_policy.hpp
|
||||
struct KernelScheduleAuto final {};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
class ArchTag,
|
||||
class OpClass,
|
||||
class ElementA,
|
||||
class GmemLayoutA,
|
||||
int AlignmentA,
|
||||
class ElementB,
|
||||
class GmemLayoutB,
|
||||
int AlignmentB,
|
||||
class ElementAccumulator,
|
||||
class TileShape_MNK,
|
||||
class ClusterShape_MNK,
|
||||
class StageCountType,
|
||||
class KernelScheduleType,
|
||||
class Enable = void
|
||||
>
|
||||
struct CollectiveBuilder {
|
||||
static_assert(sizeof(ElementA) == 0, "Could not build a collective for given parameters.");
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::collective
|
||||
|
||||
@@ -30,38 +30,8 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/detail/dependent_false.hpp"
|
||||
#include "cutlass/gemm/collective/collective_mma_decl.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
class DispatchPolicy,
|
||||
class TileShape,
|
||||
class ElementA,
|
||||
class StrideA,
|
||||
class ElementB,
|
||||
class StrideB,
|
||||
class TiledMma,
|
||||
class GmemTiledCopyA,
|
||||
class SmemLayoutAtomA,
|
||||
class SmemCopyAtomA,
|
||||
class TransformA,
|
||||
class GmemTiledCopyB,
|
||||
class SmemLayoutAtomB,
|
||||
class SmemCopyAtomB,
|
||||
class TransformB
|
||||
>
|
||||
struct CollectiveMma {
|
||||
static_assert(cutlass::detail::dependent_false<ElementA>, "Could not find a mainloop specialization.");
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::collective
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 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/numeric/integral_constant.hpp>
|
||||
#include <cutlass/detail/dependent_false.hpp>
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
class DispatchPolicy,
|
||||
class TileShape,
|
||||
class ElementA,
|
||||
class StrideA,
|
||||
class ElementB,
|
||||
class StrideB,
|
||||
class TiledMma,
|
||||
class GmemTiledCopyA,
|
||||
class SmemLayoutAtomA,
|
||||
class SmemCopyAtomA,
|
||||
class TransformA,
|
||||
class GmemTiledCopyB,
|
||||
class SmemLayoutAtomB,
|
||||
class SmemCopyAtomB,
|
||||
class TransformB
|
||||
>
|
||||
struct CollectiveMma {
|
||||
static_assert(cutlass::detail::dependent_false<ElementA>, "Could not find a mainloop specialization.");
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::collective
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
#include "cute/algorithm/gemm.hpp"
|
||||
#include "cute/atom/mma_atom.hpp"
|
||||
#include "cute/tensor_predicate.hpp"
|
||||
#include "cutlass/gemm/collective/collective_mma_decl.hpp"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
using namespace cute;
|
||||
|
||||
@@ -163,7 +164,7 @@ struct CollectiveMma<
|
||||
KTileIterator k_tile_iter, int k_tile_count,
|
||||
ResidueMNK residue_mnk,
|
||||
int thread_idx,
|
||||
char *smem_buf)
|
||||
char *smem_buf)
|
||||
{
|
||||
using namespace cute;
|
||||
|
||||
@@ -252,9 +253,9 @@ struct CollectiveMma<
|
||||
while (k_tile_count > -1)
|
||||
{
|
||||
// Pipeline the outer products with a static for loop
|
||||
for_each(make_int_sequence<K_BLOCK_MAX>{}, [&] (auto k_block)
|
||||
for_each(make_int_sequence<K_BLOCK_MAX>{}, [&] (auto k_block)
|
||||
{
|
||||
if (k_block == K_BLOCK_MAX - 1)
|
||||
if (k_block == K_BLOCK_MAX - 1)
|
||||
{
|
||||
__syncthreads();
|
||||
|
||||
@@ -268,7 +269,7 @@ struct CollectiveMma<
|
||||
int k_block_next = (k_block + Int<1>{}) % K_BLOCK_MAX; // static
|
||||
copy(tCsA(_,_,k_block_next), tCrA_copy_view(_,_,k_block_next));
|
||||
copy(tCsB(_,_,k_block_next), tCrB_copy_view(_,_,k_block_next));
|
||||
if (k_block == 0)
|
||||
if (k_block == 0)
|
||||
{
|
||||
// Copy gmem to rmem
|
||||
copy(gmem_tiled_copy_a, tAgA(_,_,_,*k_tile_iter), tArA);
|
||||
@@ -406,7 +407,7 @@ struct CollectiveMma<
|
||||
KTileIterator k_tile_iter, int k_tile_count,
|
||||
ResidueMNK residue_mnk,
|
||||
int thread_idx,
|
||||
char *smem_buf)
|
||||
char *smem_buf)
|
||||
{
|
||||
using namespace cute;
|
||||
|
||||
@@ -549,9 +550,9 @@ struct CollectiveMma<
|
||||
while (k_tile_count > -1)
|
||||
{
|
||||
// Pipeline the outer products with a static for loop
|
||||
for_each(make_int_sequence<K_BLOCK_MAX>{}, [&] (auto k_block)
|
||||
for_each(make_int_sequence<K_BLOCK_MAX>{}, [&] (auto k_block)
|
||||
{
|
||||
if (k_block == K_BLOCK_MAX - 1)
|
||||
if (k_block == K_BLOCK_MAX - 1)
|
||||
{
|
||||
__syncthreads();
|
||||
|
||||
@@ -565,7 +566,7 @@ struct CollectiveMma<
|
||||
int k_block_next = (k_block + Int<1>{}) % K_BLOCK_MAX; // static
|
||||
copy(tCsA(_,_,k_block_next), tCrA_copy_view(_,_,k_block_next));
|
||||
copy(tCsB(_,_,k_block_next), tCrB_copy_view(_,_,k_block_next));
|
||||
if (k_block == 0)
|
||||
if (k_block == 0)
|
||||
{
|
||||
if (k_tile_count <= 0) {
|
||||
clear(tApA);
|
||||
|
||||
@@ -290,7 +290,7 @@ struct CollectiveMma<
|
||||
}
|
||||
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > -(DispatchPolicy::Stages-1); --k_tile_count)
|
||||
while (k_tile_count > -(DispatchPolicy::Stages-1))
|
||||
{
|
||||
// Pipeline the outer products with a static for loop.
|
||||
//
|
||||
@@ -318,6 +318,9 @@ struct CollectiveMma<
|
||||
copy(gmem_tiled_copy_A, tAgA(_,_,_,*k_tile_iter), tAsA(_,_,_,smem_pipe_write));
|
||||
copy(gmem_tiled_copy_B, tBgB(_,_,_,*k_tile_iter), tBsB(_,_,_,smem_pipe_write));
|
||||
cp_async_fence();
|
||||
|
||||
// Advance the tile
|
||||
--k_tile_count;
|
||||
if (k_tile_count > 0) { ++k_tile_iter; }
|
||||
|
||||
// Advance the pipe -- Doing it here accounts for K_BLOCK_MAX = 1 (no rmem pipe)
|
||||
@@ -344,6 +347,7 @@ struct CollectiveMma<
|
||||
|
||||
template <
|
||||
int Stages,
|
||||
class ClusterShape_,
|
||||
class TileShape_,
|
||||
class ElementA_,
|
||||
class StrideA_,
|
||||
@@ -360,7 +364,9 @@ template <
|
||||
class TransformB_
|
||||
>
|
||||
struct CollectiveMma<
|
||||
MainloopSm80CpAsync<Stages>,
|
||||
MainloopSm80CpAsync<
|
||||
Stages,
|
||||
ClusterShape_>,
|
||||
TileShape_,
|
||||
ElementA_,
|
||||
StrideA_,
|
||||
@@ -380,7 +386,9 @@ struct CollectiveMma<
|
||||
//
|
||||
// Type Aliases
|
||||
//
|
||||
using DispatchPolicy = MainloopSm80CpAsync<Stages>;
|
||||
using DispatchPolicy = MainloopSm80CpAsync<
|
||||
Stages,
|
||||
ClusterShape_>;
|
||||
using TileShape = TileShape_;
|
||||
// Follow the change in TestSmall: TileShape switch to CtaShape
|
||||
// In legacy arch, it should be same
|
||||
@@ -490,8 +498,8 @@ struct CollectiveMma<
|
||||
|
||||
// Shift tensor so residue_k is at origin (Can't read any k_coord < residue_k)
|
||||
// This aligns the tensor with BLK_K for all but the 0th k_tile
|
||||
gA.data() = &gA(0, get<2>(residue_mnk), 0);
|
||||
gB.data() = &gB(0, get<2>(residue_mnk), 0);
|
||||
gA = cute::domain_offset(make_coord(0, get<2>(residue_mnk), 0), gA);
|
||||
gB = cute::domain_offset(make_coord(0, get<2>(residue_mnk), 0), gB);
|
||||
|
||||
// Partition the copying of A and B tiles across the threads
|
||||
GmemTiledCopyA gmem_tiled_copy_A;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/cuda_host_adapter.hpp"
|
||||
|
||||
#include "cute/arch/cluster_sm90.hpp"
|
||||
#include "cute/arch/copy_sm90.hpp"
|
||||
@@ -94,10 +95,10 @@ struct CollectiveMma<
|
||||
using TileShape = TileShape_;
|
||||
using ElementA = ElementA_;
|
||||
using StrideA = StrideA_;
|
||||
using UnderlyingStrideA = cute::remove_pointer_t<StrideA>;
|
||||
using InternalStrideA = cute::remove_pointer_t<StrideA>;
|
||||
using ElementB = ElementB_;
|
||||
using StrideB = StrideB_;
|
||||
using UnderlyingStrideB = cute::remove_pointer_t<StrideB>;
|
||||
using InternalStrideB = cute::remove_pointer_t<StrideB>;
|
||||
using TiledMma = TiledMma_;
|
||||
using ElementAccumulator = typename TiledMma::ValTypeC;
|
||||
using GmemTiledCopyA = GmemTiledCopyA_;
|
||||
@@ -152,14 +153,14 @@ struct CollectiveMma<
|
||||
// Assumption: StrideA is congruent with Problem_MK
|
||||
using TMA_A = decltype(make_tma_copy(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(static_cast<InternalElementA const*>(nullptr), repeat_like(UnderlyingStrideA{}, int32_t(0)), UnderlyingStrideA{}),
|
||||
make_tensor(static_cast<InternalElementA const*>(nullptr), repeat_like(InternalStrideA{}, int32_t(0)), InternalStrideA{}),
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{}))); // mcast along N mode for this M load, if any
|
||||
// Assumption: StrideB is congruent with Problem_NK
|
||||
using TMA_B = decltype(make_tma_copy(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(static_cast<InternalElementB const*>(nullptr), repeat_like(UnderlyingStrideB{}, int32_t(0)), UnderlyingStrideB{}),
|
||||
make_tensor(static_cast<InternalElementB const*>(nullptr), repeat_like(InternalStrideB{}, int32_t(0)), InternalStrideB{}),
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{}))); // mcast along M mode for this N load, if any
|
||||
@@ -182,7 +183,7 @@ struct CollectiveMma<
|
||||
using TensorMapStorage = typename SharedStorage::TensorMapStorage;
|
||||
using PipelineStorage = typename SharedStorage::PipelineStorage;
|
||||
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<UnderlyingStrideA, StrideA>;
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<InternalStrideA, StrideA>;
|
||||
|
||||
// Host side kernel arguments
|
||||
struct Arguments {
|
||||
@@ -196,6 +197,7 @@ struct CollectiveMma<
|
||||
struct Params {
|
||||
TMA_A tma_load_a;
|
||||
TMA_B tma_load_b;
|
||||
uint32_t tma_transaction_bytes = TmaTransactionBytes;
|
||||
void* tensormaps;
|
||||
InternalElementA const** ptr_A;
|
||||
StrideA dA;
|
||||
@@ -222,14 +224,14 @@ struct CollectiveMma<
|
||||
// Batches/Groups are managed by using appropriate pointers to input matrices
|
||||
const uint32_t mock_L = 1;
|
||||
InternalElementA const* ptr_A_first_batch = reinterpret_cast<InternalElementA const*>(args.ptr_A);
|
||||
InternalElementB const* ptr_B_first_batch = reinterpret_cast<InternalElementA const*>(args.ptr_B);
|
||||
InternalElementB const* ptr_B_first_batch = reinterpret_cast<InternalElementB const*>(args.ptr_B);
|
||||
|
||||
UnderlyingStrideA stride_a;
|
||||
UnderlyingStrideB stride_b;
|
||||
InternalStrideA stride_a;
|
||||
InternalStrideB stride_b;
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
// Strides for Grouped Gemm will be replaced prior to the first access regardless.
|
||||
stride_a = UnderlyingStrideA{};
|
||||
stride_b = UnderlyingStrideB{};
|
||||
stride_a = InternalStrideA{};
|
||||
stride_b = InternalStrideB{};
|
||||
}
|
||||
else {
|
||||
// Tensor shapes for Ptr-Array are initialized correctly only here.
|
||||
@@ -261,6 +263,7 @@ struct CollectiveMma<
|
||||
return {
|
||||
tma_load_a,
|
||||
tma_load_b,
|
||||
TmaTransactionBytes,
|
||||
tensormaps,
|
||||
reinterpret_cast<InternalElementA const**>(args.ptr_A),
|
||||
args.dA,
|
||||
@@ -280,12 +283,12 @@ struct CollectiveMma<
|
||||
|
||||
template <class ProblemShape>
|
||||
static cutlass::Status
|
||||
initialize_workspace(ProblemShape const& problem_shape, Arguments const& args, void* workspace, cudaStream_t stream) {
|
||||
initialize_workspace(ProblemShape const& problem_shape, Arguments const& args, void* workspace, cudaStream_t stream, CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
return cutlass::Status::kSuccess;
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape problem_shapes,
|
||||
Arguments const& args) {
|
||||
@@ -299,8 +302,8 @@ struct CollectiveMma<
|
||||
for (int i = 0; i < problem_shapes.groups(); i++) {
|
||||
auto problem_shape_MNKL = append<4>(problem_shapes.get_host_problem_shape(i), 1);
|
||||
auto [M,N,K,L] = problem_shape_MNKL;
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_A>(cute::make_shape(M,K,L), UnderlyingStrideA{});
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_B>(cute::make_shape(N,K,L), UnderlyingStrideB{});
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_A>(cute::make_shape(M,K,L), InternalStrideA{});
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_B>(cute::make_shape(N,K,L), InternalStrideB{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,8 +483,22 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::ALayout{}) == 0 and
|
||||
stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::ALayout{}) == NumThreadsPerWarpGroup and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsA = thread_mma.partition_A(sA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
@@ -508,12 +525,9 @@ struct CollectiveMma<
|
||||
|
||||
// Prologue GMMAs
|
||||
int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
|
||||
assert(k_tile_count >= 1);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count; k_tile_prologue > 0; --k_tile_prologue)
|
||||
{
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
@@ -534,6 +548,22 @@ struct CollectiveMma<
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count - 1; k_tile_prologue > 0; --k_tile_prologue)
|
||||
{
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
pipeline.consumer_wait(smem_pipe_read, barrier_token);
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
warpgroup_arrive();
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum); // (V,M,K) x (V,N,K) => (V,M,N)
|
||||
warpgroup_commit_batch();
|
||||
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
// Mainloop GMMAs
|
||||
k_tile_count -= prologue_mma_count;
|
||||
@@ -552,13 +582,7 @@ struct CollectiveMma<
|
||||
int read_stage = smem_pipe_read.index();
|
||||
warpgroup_fence_operand(accum);
|
||||
warpgroup_arrive();
|
||||
// Unroll the K mode manually to set scale D to 1
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accum);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum); // (V,M,K) x (V,N,K) => (V,M,N)
|
||||
warpgroup_commit_batch();
|
||||
|
||||
/// Wait on the GMMA barrier for K_PIPE_MMAS (or fewer) outstanding to ensure smem_pipe_write is consumed
|
||||
@@ -730,11 +754,12 @@ struct CollectiveMma<
|
||||
tensormaps_cp_fence_release (
|
||||
TensorMapStorage& shared_tensormap,
|
||||
cute::tuple<TensorMapA, TensorMapB> const& input_tensormaps) {
|
||||
// Entire warp must do this (ie its aligned)
|
||||
// Entire warp must do this (i.e. it's aligned)
|
||||
tma_descriptor_cp_fence_release(get<0>(input_tensormaps), shared_tensormap.smem_tensormap_A);
|
||||
tma_descriptor_cp_fence_release(get<1>(input_tensormaps), shared_tensormap.smem_tensormap_B);
|
||||
}
|
||||
|
||||
// The entire warp must call this function collectively (that is, the instructions are aligned)
|
||||
template <class TensorMapA, class TensorMapB>
|
||||
CUTLASS_DEVICE
|
||||
void
|
||||
|
||||
@@ -245,7 +245,7 @@ struct CollectiveMma<
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -445,14 +445,27 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto mma_thread_slice = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto mma_warpgroup_slice = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
// Allocate fragments and descriptors
|
||||
Tensor tCsA = thread_mma.partition_A(sA);
|
||||
Tensor tCrA = thread_mma.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(gmma_sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = thread_mma.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCsA = mma_thread_slice.partition_A(sA);
|
||||
Tensor tCrA = mma_thread_slice.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = mma_warpgroup_slice.partition_B(gmma_sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = mma_warpgroup_slice.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
//
|
||||
// Copy Atom A retiling
|
||||
|
||||
@@ -172,7 +172,7 @@ struct CollectiveMma<
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -361,8 +361,22 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::ALayout{}) == 0 and
|
||||
stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::ALayout{}) == NumThreadsPerWarpGroup and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsA = thread_mma.partition_A(sA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
@@ -389,13 +403,10 @@ struct CollectiveMma<
|
||||
|
||||
// Prologue GMMAs
|
||||
int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
|
||||
assert(k_tile_count >= 1);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count; k_tile_prologue > 0; --k_tile_prologue) {
|
||||
|
||||
{
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
pipeline.consumer_wait(smem_pipe_read, barrier_token);
|
||||
@@ -417,6 +428,26 @@ struct CollectiveMma<
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count - 1; k_tile_prologue > 0; --k_tile_prologue) {
|
||||
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
pipeline.consumer_wait(smem_pipe_read, barrier_token);
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
|
||||
warpgroup_arrive();
|
||||
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum);
|
||||
|
||||
warpgroup_commit_batch();
|
||||
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
|
||||
// Mainloop GMMAs
|
||||
@@ -433,14 +464,8 @@ struct CollectiveMma<
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
warpgroup_arrive();
|
||||
|
||||
// Unroll the K mode manually to set scale D to 1
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accum);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum);
|
||||
warpgroup_commit_batch();
|
||||
|
||||
/// Wait on the GMMA barrier for K_PIPE_MMAS (or fewer) outstanding to ensure smem_pipe_write is consumed
|
||||
|
||||
@@ -110,7 +110,8 @@ struct CollectiveMma<
|
||||
using SmemCopyAtomB = SmemCopyAtomB_;
|
||||
|
||||
using CtaShape_MNK = decltype(shape_div(TileShape{}, ClusterShape{}));
|
||||
// Swap and transpose A/B for A k-major layout and B mn-major layout since WGMMA is k-major only (e.g. tf32, Fp32, Int8, Fp8 WGMMA)
|
||||
// Swap and transpose A/B for A k-major layout and B mn-major layout since WGMMA is k-major only
|
||||
// (e.g. tf32, Fp32, Int8, Fp8 WGMMA)
|
||||
static constexpr bool IsLayoutAkBmn =
|
||||
cute::is_same_v<gemm::detail::StrideToLayoutTagA_t<StrideA>, layout::RowMajor> &&
|
||||
cute::is_same_v<gemm::detail::StrideToLayoutTagB_t<StrideB>, layout::RowMajor>;
|
||||
@@ -235,21 +236,24 @@ struct CollectiveMma<
|
||||
// Device side kernel params
|
||||
struct Params {
|
||||
// Assumption: StrideA is congruent with Problem_MK
|
||||
using TMA_A = decltype(make_tma_copy(
|
||||
using TMA_A = decltype(make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(static_cast<InternalElementA const*>(nullptr), repeat_like(InternalStrideA{}, int32_t(0)), InternalStrideA{}),
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{}))); // mcast along N mode for this M load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
// Assumption: StrideB is congruent with Problem_NK
|
||||
using TMA_B = decltype(make_tma_copy(
|
||||
using TMA_B = decltype(make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(static_cast<InternalElementB const*>(nullptr), repeat_like(InternalStrideB{}, int32_t(0)), InternalStrideB{}),
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{}))); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
TMA_A tma_load_a;
|
||||
TMA_B tma_load_b;
|
||||
uint32_t tma_transaction_bytes = TmaTransactionBytes;
|
||||
uint32_t tma_transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t tma_transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -290,26 +294,33 @@ struct CollectiveMma<
|
||||
|
||||
Tensor tensor_a = make_tensor(ptr_A, make_layout(make_shape(M,K,L), dA));
|
||||
Tensor tensor_b = make_tensor(ptr_B, make_layout(make_shape(N,K,L), dB));
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy(
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
tensor_a,
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{})); // mcast along N mode for this M load, if any
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy(
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
tensor_b,
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{})); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
uint32_t transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
uint32_t transaction_bytes = transaction_bytes_mk + transaction_bytes_nk;
|
||||
|
||||
return {
|
||||
tma_load_a,
|
||||
tma_load_b
|
||||
tma_load_b,
|
||||
transaction_bytes,
|
||||
transaction_bytes_mk,
|
||||
transaction_bytes_nk
|
||||
};
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -330,9 +341,11 @@ struct CollectiveMma<
|
||||
}
|
||||
|
||||
static constexpr int K_PIPE_MAX = DispatchPolicy::Stages;
|
||||
static constexpr uint32_t TmaTransactionBytes =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<InternalElementA>::value)) +
|
||||
static constexpr uint32_t TmaTransactionBytesMK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<InternalElementA>::value));
|
||||
static constexpr uint32_t TmaTransactionBytesNK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * static_cast<uint32_t>(sizeof_bits<InternalElementB>::value)) ;
|
||||
static constexpr uint32_t TmaTransactionBytes = TmaTransactionBytesMK + TmaTransactionBytesNK;
|
||||
|
||||
/// Issue Tma Descriptor Prefetch -- ideally from a single thread for best performance
|
||||
CUTLASS_DEVICE
|
||||
@@ -375,7 +388,7 @@ struct CollectiveMma<
|
||||
CUTLASS_DEVICE void
|
||||
load(
|
||||
Params const& mainloop_params,
|
||||
MainloopPipeline pipeline,
|
||||
MainloopPipeline pipeline,
|
||||
PipelineState smem_pipe_write,
|
||||
cute::tuple<TensorA, TensorB> const& load_inputs,
|
||||
BlockCoord const& blk_coord,
|
||||
@@ -422,14 +435,14 @@ struct CollectiveMma<
|
||||
// Issue TmaLoads
|
||||
// Maps the tile -> block, value
|
||||
if constexpr (cute::is_same_v<GmemTiledCopyA, SM90_TMA_LOAD_MULTICAST>) {
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
for (int n = 0; n < size<1>(block_layout); ++n) {
|
||||
mcast_mask_a |= (uint16_t(1) << block_layout(cluster_local_block_id.x,n,Int<0>{}));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (cute::is_same_v<GmemTiledCopyB, SM90_TMA_LOAD_MULTICAST>) {
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
for (int m = 0; m < size<0>(block_layout); ++m) {
|
||||
mcast_mask_b |= (uint16_t(1) << block_layout(m,cluster_local_block_id.y,Int<0>{}));
|
||||
}
|
||||
@@ -518,14 +531,27 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto mma_thread_slice = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto mma_warpgroup_slice = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
// Allocate fragments and descriptors
|
||||
Tensor tCsA = thread_mma.partition_A(sA);
|
||||
Tensor tCrA = thread_mma.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(gmma_sB_position_dependent); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = thread_mma.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCsA = mma_thread_slice.partition_A(sA);
|
||||
Tensor tCrA = mma_thread_slice.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = mma_warpgroup_slice.partition_B(gmma_sB_position_dependent); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = mma_warpgroup_slice.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
//
|
||||
// Copy Atom A retiling
|
||||
|
||||
+74
-54
@@ -39,6 +39,7 @@
|
||||
#include "cutlass/detail/layout.hpp"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/transform/collective/sm90_wgmma_transpose.hpp"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/detail/collective.hpp"
|
||||
|
||||
@@ -50,9 +51,6 @@
|
||||
#include "cute/algorithm/gemm.hpp"
|
||||
#include "cute/tensor_predicate.hpp"
|
||||
#include "cute/numeric/arithmetic_tuple.hpp"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/detail/collective.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -128,7 +126,8 @@ public:
|
||||
using TileShape = TileShape_;
|
||||
|
||||
static_assert(cute::is_tuple<ElementAOptionalTuple>::value ^ cute::is_tuple<ElementBOptionalTuple>::value,
|
||||
"Either A OR B must be a tuple. It must take the from {ElementOperand, [ElementScale], [ElementZero]}. Inputs in [] are optional.");
|
||||
"Either A OR B must be a tuple. It must take the from {ElementOperand, [ElementScale],"
|
||||
"[ElementZero]}. Inputs in [] are optional.");
|
||||
|
||||
using ElementA = detail::deduce_mixed_width_dtype_t<0, ElementAOptionalTuple>;
|
||||
using ElementB = detail::deduce_mixed_width_dtype_t<0, ElementBOptionalTuple>;
|
||||
@@ -144,7 +143,8 @@ public:
|
||||
// These are always MN major
|
||||
using StrideScale = cute::Stride<cute::Int<1>, int64_t, int64_t>;
|
||||
// For cases where we can't have a void scale, we can use this to allow the code to compile when the scale is void.
|
||||
using NonVoidStrideScale = cute::conditional_t<cute::is_void_v<StrideScale>, cute::Stride<_1, int64_t, int64_t>, StrideScale>;
|
||||
using NonVoidStrideScale = cute::conditional_t<
|
||||
cute::is_void_v<StrideScale>, cute::Stride<_1, int64_t, int64_t>, StrideScale>;
|
||||
|
||||
static_assert((IsATransformed && cutlass::gemm::detail::is_k_major<StrideA>()) ||
|
||||
(!IsATransformed && cutlass::gemm::detail::is_k_major<StrideB>()),
|
||||
@@ -303,11 +303,8 @@ private:
|
||||
|
||||
// These methods use some the public members of the class. For that reason, we define them after the public section.
|
||||
static constexpr uint32_t
|
||||
compute_tma_transaction_bytes() {
|
||||
constexpr uint32_t a_bytes = cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(cute::sizeof_bits_v<InternalElementA>));
|
||||
constexpr uint32_t b_bytes = cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * static_cast<uint32_t>(cute::sizeof_bits_v<InternalElementB>));
|
||||
|
||||
constexpr uint32_t baseline_bytes = a_bytes + b_bytes;
|
||||
compute_tma_transaction_bytes_mk() {
|
||||
constexpr uint32_t baseline_bytes = cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(cute::sizeof_bits_v<InternalElementA>));
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
return baseline_bytes;
|
||||
@@ -333,6 +330,11 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t
|
||||
compute_tma_transaction_bytes_nk() {
|
||||
return cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * static_cast<uint32_t>(cute::sizeof_bits_v<InternalElementB>));
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr size_t SmemAlignmentA = cutlass::detail::alignment_for_swizzle(SmemLayoutA{});
|
||||
|
||||
@@ -421,6 +423,9 @@ public:
|
||||
TMA_Zero tma_load_zero;
|
||||
int64_t scale_k;
|
||||
int group_size;
|
||||
uint32_t tma_transaction_bytes = TmaTransactionBytes;
|
||||
uint32_t tma_transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t tma_transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -478,7 +483,7 @@ public:
|
||||
typename Params::TMA_Scale tma_load_scale;
|
||||
typename Params::TMA_Zero tma_load_zero;
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, 0, 0 };
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, 0, 0, TmaTransactionBytes, TmaTransactionBytesMK, TmaTransactionBytesNK };
|
||||
}
|
||||
else if constexpr (ModeHasScales) {
|
||||
auto scale_k = (K + args.group_size - 1) / args.group_size;
|
||||
@@ -493,7 +498,7 @@ public:
|
||||
_1{}); // mcast along N mode for this M load, if any
|
||||
|
||||
if constexpr(KernelConversionMode == ConversionMode::ConvertAndScale) {
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, scale_k, args.group_size };
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, scale_k, args.group_size, TmaTransactionBytes, TmaTransactionBytesMK, TmaTransactionBytesNK };
|
||||
}
|
||||
else if constexpr(KernelConversionMode == ConversionMode::ConvertAndScaleWithZero) {
|
||||
Tensor tensor_zero = make_tensor(get_logical_ptr(args.ptr_Z), make_layout(make_shape(M,scale_k,L), dS));
|
||||
@@ -503,7 +508,7 @@ public:
|
||||
SmemLayoutScale{}(_,_,cute::Int<0>{}),
|
||||
ScaleTileShape{},
|
||||
_1{}); // mcast along N mode for this M load, if any
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, scale_k, args.group_size };
|
||||
return { tma_load_a, tma_load_b, tma_load_scale, tma_load_zero, scale_k, args.group_size, TmaTransactionBytes, TmaTransactionBytesMK, TmaTransactionBytesNK };
|
||||
} else {
|
||||
static_assert(cutlass::detail::dependent_false<KernelSchedule>, "Conversion mode not handled in to_underlying_arguments.");
|
||||
}
|
||||
@@ -514,7 +519,7 @@ public:
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -564,7 +569,9 @@ public:
|
||||
}
|
||||
|
||||
static constexpr int K_PIPE_MAX = DispatchPolicy::Stages;
|
||||
static constexpr uint32_t TmaTransactionBytes = compute_tma_transaction_bytes();
|
||||
static constexpr uint32_t TmaTransactionBytesMK = compute_tma_transaction_bytes_mk();
|
||||
static constexpr uint32_t TmaTransactionBytesNK = compute_tma_transaction_bytes_nk();
|
||||
static constexpr uint32_t TmaTransactionBytes = TmaTransactionBytesMK + TmaTransactionBytesNK;
|
||||
|
||||
/// Issue Tma Descriptor Prefetch -- ideally from a single thread for best performance
|
||||
CUTLASS_DEVICE
|
||||
@@ -607,22 +614,22 @@ public:
|
||||
Tensor mB_nkl = mainloop_params.tma_load_b.get_tma_tensor(make_shape(N,K,L)); // (n,k,l)
|
||||
|
||||
// Make tiled views, defer the slice
|
||||
Tensor gA_mkl = local_tile(mA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor gB_nkl = local_tile(mB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
Tensor gA_mkl = local_tile(mA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor gB_nkl = local_tile(mB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
return cute::make_tuple(gA_mkl, gB_nkl);
|
||||
}
|
||||
else if constexpr (ModeHasScales) {
|
||||
auto scale_k = mainloop_params.scale_k;
|
||||
Tensor mS_mkl = mainloop_params.tma_load_scale.get_tma_tensor(make_shape(M,scale_k,L)); // (m,scale_k,l)
|
||||
Tensor gS_mkl = local_tile(mS_mkl, ScaleTileShape{}, make_coord(_,_)); // (BLK_M,BLK_Scale_K,m,scale_k,l)
|
||||
Tensor mS_mkl = mainloop_params.tma_load_scale.get_tma_tensor(make_shape(M,scale_k,L)); // (m,scale_k,l)
|
||||
Tensor gS_mkl = local_tile(mS_mkl, ScaleTileShape{}, make_coord(_,_)); // (BLK_M,BLK_Scale_K,m,scale_k,l)
|
||||
if constexpr (KernelConversionMode == ConversionMode::ConvertAndScale) {
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, gS_mkl);
|
||||
}
|
||||
else if constexpr (KernelConversionMode == ConversionMode::ConvertAndScaleWithZero) {
|
||||
Tensor mZ_mkl = mainloop_params.tma_load_zero.get_tma_tensor(make_shape(M,scale_k,L)); // (m,scale_k,l)
|
||||
Tensor gZ_mkl = local_tile(mZ_mkl, ScaleTileShape{}, make_coord(_,_)); // (BLK_M,BLK_Scale_K,m,scale_k,l)
|
||||
Tensor mZ_mkl = mainloop_params.tma_load_zero.get_tma_tensor(make_shape(M,scale_k,L)); // (m,scale_k,l)
|
||||
Tensor gZ_mkl = local_tile(mZ_mkl, ScaleTileShape{}, make_coord(_,_)); // (BLK_M,BLK_Scale_K,m,scale_k,l)
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, gS_mkl, gZ_mkl);
|
||||
}
|
||||
else {
|
||||
@@ -668,10 +675,10 @@ public:
|
||||
int lane_predicate = cute::elect_one_sync();
|
||||
|
||||
if (lane_predicate) {
|
||||
Tensor sA_ = make_tensor(make_smem_ptr(shared_tensors.smem_A.begin()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB_ = make_tensor(make_smem_ptr(shared_tensors.smem_B.begin()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sA = as_position_independent_swizzle_tensor(sA_); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = as_position_independent_swizzle_tensor(sB_); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sA_ = make_tensor(make_smem_ptr(shared_tensors.smem_A.begin()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB_ = make_tensor(make_smem_ptr(shared_tensors.smem_B.begin()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sA = as_position_independent_swizzle_tensor(sA_); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = as_position_independent_swizzle_tensor(sB_); // (BLK_N,BLK_K,PIPE)
|
||||
|
||||
//
|
||||
// Prepare the TMA loads for A, B and Scales
|
||||
@@ -692,10 +699,10 @@ public:
|
||||
Tensor gB = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
|
||||
// Applies the mapping from block_tma_a
|
||||
Tensor tAgA = block_tma_a.partition_S(gA); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tAgA = block_tma_a.partition_S(gA); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tAsA = block_tma_a.partition_D(sA); // (TMA,TMA_M,TMA_K,PIPE)
|
||||
|
||||
Tensor tBgB = block_tma_b.partition_S(gB); // (TMA,TMA_N,TMA_K,k)
|
||||
Tensor tBgB = block_tma_b.partition_S(gB); // (TMA,TMA_N,TMA_K,k)
|
||||
Tensor tBsB = block_tma_b.partition_D(sB); // (TMA,TMA_N,TMA_K,PIPE)
|
||||
|
||||
uint16_t mcast_mask_a = 0;
|
||||
@@ -705,14 +712,14 @@ public:
|
||||
// Issue TmaLoads
|
||||
// Maps the tile -> block, value
|
||||
if constexpr (cute::is_same_v<GmemTiledCopyA, SM90_TMA_LOAD_MULTICAST>) {
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
for (int n = 0; n < size<1>(block_layout); ++n) {
|
||||
mcast_mask_a |= (uint16_t(1) << block_layout(cluster_local_block_id.x,n,Int<0>{}));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (cute::is_same_v<GmemTiledCopyB, SM90_TMA_LOAD_MULTICAST>) {
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
auto block_layout = Layout<typename DispatchPolicy::ClusterShape>{}; // (m,n) -> block_id
|
||||
for (int m = 0; m < size<0>(block_layout); ++m) {
|
||||
mcast_mask_b |= (uint16_t(1) << block_layout(m,cluster_local_block_id.y,Int<0>{}));
|
||||
}
|
||||
@@ -829,16 +836,29 @@ public:
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
Tensor tCsA = thread_mma.partition_A(sA);
|
||||
auto mma_thread_slice = tiled_mma.get_thread_slice(thread_idx);
|
||||
Tensor tCsA = mma_thread_slice.partition_A(sA);
|
||||
auto mma_warpgroup_slice = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
// Allocate fragments and descriptors
|
||||
Tensor tCrA_mma = thread_mma.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCrA_mma = mma_thread_slice.partition_fragment_A(sA(_,_,Int<0>{})); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCrA_load = make_fragment_like<RealInternalElementA>(tCrA_mma);
|
||||
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = thread_mma.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCsB = mma_warpgroup_slice.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrB = mma_warpgroup_slice.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
//
|
||||
// Copy Atom A retiling
|
||||
@@ -846,7 +866,7 @@ public:
|
||||
auto smem_tiled_copy_A = make_tiled_copy_A(InternalSmemCopyAtomA{}, tiled_mma);
|
||||
auto smem_thr_copy_A = smem_tiled_copy_A.get_thread_slice(warp_group_thread_idx);
|
||||
|
||||
Tensor tCrA_copy_view = smem_thr_copy_A.retile_D(tCrA_load); // (CPY,CPY_M,CPY_K)
|
||||
Tensor tCrA_copy_view = smem_thr_copy_A.retile_D(tCrA_load); // (CPY,CPY_M,CPY_K)
|
||||
|
||||
// Compute the max vector length that can be used to copy A. This will match the vector width of the
|
||||
// conversions used. It helps by allowing the compiler to convert using the same register that was used
|
||||
@@ -856,7 +876,7 @@ public:
|
||||
using A_CPY_VEC = decltype(max_common_vector(tCsA, tCrA_copy_view));
|
||||
|
||||
// Partition of thread -> shared and thread -> RF
|
||||
auto partitioned_extra_info = partition_extra_mma_info(thread_mma, shared_tensors);
|
||||
auto partitioned_extra_info = partition_extra_mma_info(mma_thread_slice, shared_tensors);
|
||||
auto copy_partitions_extra_info = retile_extra_mma_info(tiled_mma, partitioned_extra_info, warp_group_thread_idx);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsA) == size<1>(tCrA_copy_view)); // CPY_M
|
||||
@@ -1047,16 +1067,16 @@ private:
|
||||
int const l_coord) {
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
return cute::tuple{};
|
||||
return cute::make_tuple();
|
||||
}
|
||||
else if constexpr (ModeHasScales) {
|
||||
Tensor sS = make_tensor(make_smem_ptr(shared_tensors.smem_scale.begin()), SmemLayoutScale{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor gS_mkl = get<2>(load_inputs);
|
||||
auto block_tma_s = mainloop_params.tma_load_scale.get_slice(cluster_local_block_id.y);
|
||||
Tensor gS = gS_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gS = gS_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
|
||||
Tensor tSgS = block_tma_s.partition_S(gS); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tSsS = block_tma_s.partition_D(sS); // (TMA,TMA_M,TMA_K,PIPE)
|
||||
Tensor tSgS = block_tma_s.partition_S(gS); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tSsS = block_tma_s.partition_D(sS); // (TMA,TMA_M,TMA_K,PIPE)
|
||||
if constexpr (KernelConversionMode == ConversionMode::ConvertAndScale) {
|
||||
return cute::make_tuple(tSgS, tSsS);
|
||||
}
|
||||
@@ -1064,10 +1084,10 @@ private:
|
||||
Tensor sZ = make_tensor(make_smem_ptr(shared_tensors.smem_zero.begin()), SmemLayoutScale{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor gZ_mkl = get<3>(load_inputs);
|
||||
auto block_tma_z = mainloop_params.tma_load_zero.get_slice(cluster_local_block_id.y);
|
||||
Tensor gZ = gZ_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gZ = gZ_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
|
||||
Tensor tZgZ = block_tma_z.partition_S(gZ); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tZsZ = block_tma_z.partition_D(sZ); // (TMA,TMA_M,TMA_K,PIPE)
|
||||
Tensor tZgZ = block_tma_z.partition_S(gZ); // (TMA,TMA_M,TMA_K,k)
|
||||
Tensor tZsZ = block_tma_z.partition_D(sZ); // (TMA,TMA_M,TMA_K,PIPE)
|
||||
return cute::make_tuple(tSgS, tSsS, tZgZ, tZsZ);
|
||||
}
|
||||
else {
|
||||
@@ -1083,25 +1103,25 @@ private:
|
||||
template <class ThreadMma>
|
||||
CUTLASS_DEVICE
|
||||
auto partition_extra_mma_info(
|
||||
ThreadMma const& thread_mma,
|
||||
ThreadMma const& mma_thread_slice,
|
||||
TensorStorage& shared_tensors) {
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
// noting to do
|
||||
return cute::tuple{};
|
||||
// nothing to do
|
||||
return cute::make_tuple();
|
||||
}
|
||||
else if constexpr (ModeHasScales) {
|
||||
Tensor sS = make_tensor(make_smem_ptr(shared_tensors.smem_scale.begin()), SmemLayoutScale{}); // (BLK_M,BLK_SCALE_K,PIPE)
|
||||
Tensor tCsS = thread_mma.partition_A(sS);
|
||||
Tensor tCrS = make_tensor<ElementScale>(thread_mma.partition_fragment_A(sS(_,_,Int<0>{})).shape());
|
||||
Tensor sS = make_tensor(make_smem_ptr(shared_tensors.smem_scale.begin()), SmemLayoutScale{});// (BLK_M,BLK_SCALE_K,PIPE)
|
||||
Tensor tCsS = mma_thread_slice.partition_A(sS);
|
||||
Tensor tCrS = make_tensor<ElementScale>(mma_thread_slice.partition_fragment_A(sS(_,_,Int<0>{})).shape());
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::ConvertAndScale) {
|
||||
return cute::make_tuple(tCsS, tCrS);
|
||||
}
|
||||
else if constexpr (KernelConversionMode == ConversionMode::ConvertAndScaleWithZero) {
|
||||
Tensor sZ = make_tensor(make_smem_ptr(shared_tensors.smem_zero.begin()), SmemLayoutScale{}); // (BLK_M,BLK_SCALE_K,PIPE)
|
||||
Tensor tCsZ = thread_mma.partition_A(sZ);
|
||||
Tensor tCrZ = make_tensor<ElementZero>(thread_mma.partition_fragment_A(sZ(_,_,Int<0>{})).shape());
|
||||
Tensor sZ = make_tensor(make_smem_ptr(shared_tensors.smem_zero.begin()), SmemLayoutScale{});// (BLK_M,BLK_SCALE_K,PIPE)
|
||||
Tensor tCsZ = mma_thread_slice.partition_A(sZ);
|
||||
Tensor tCrZ = make_tensor<ElementZero>(mma_thread_slice.partition_fragment_A(sZ(_,_,Int<0>{})).shape());
|
||||
return cute::make_tuple(tCsS, tCrS, tCsZ, tCrZ);
|
||||
}
|
||||
else {
|
||||
@@ -1122,8 +1142,8 @@ private:
|
||||
int const warp_group_thread_idx) {
|
||||
|
||||
if constexpr (KernelConversionMode == ConversionMode::DirectConvert) {
|
||||
// noting to do
|
||||
return cute::tuple{};
|
||||
// nothing to do
|
||||
return cute::make_tuple();
|
||||
}
|
||||
else if constexpr (ModeHasScales) {
|
||||
auto smem_tiled_copy_S = make_tiled_copy_A(SmemCopyAtomScale{}, tiled_mma);
|
||||
|
||||
@@ -223,7 +223,7 @@ struct CollectiveMma<
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -399,8 +399,22 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::ALayout{}) == 0 and
|
||||
stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::ALayout{}) == NumThreadsPerWarpGroup and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsA = thread_mma.partition_A(sA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
@@ -424,9 +438,7 @@ struct CollectiveMma<
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
// Prologue MMAs
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
prologue_mma_count > 0; --prologue_mma_count)
|
||||
assert(k_tile_count >= 1);
|
||||
{
|
||||
// WAIT on smem_pipe_read until it's data is available
|
||||
pipeline.consumer_wait(smem_pipe_read);
|
||||
@@ -443,6 +455,20 @@ struct CollectiveMma<
|
||||
++smem_pipe_read;
|
||||
--k_tile_count;
|
||||
}
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count) - 1;
|
||||
prologue_mma_count > 0; --prologue_mma_count)
|
||||
{
|
||||
// WAIT on smem_pipe_read until it's data is available
|
||||
pipeline.consumer_wait(smem_pipe_read);
|
||||
warpgroup_arrive();
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,smem_pipe_read.index()), tCrB(_,_,_,smem_pipe_read.index()), accum);
|
||||
warpgroup_commit_batch();
|
||||
++smem_pipe_read;
|
||||
--k_tile_count;
|
||||
}
|
||||
warpgroup_fence_operand(accum);
|
||||
|
||||
//
|
||||
@@ -461,13 +487,8 @@ struct CollectiveMma<
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
warpgroup_arrive();
|
||||
// Unroll the K mode manually to set scale D to 1
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,smem_pipe_read.index()), tCrB(_,_,k_block,smem_pipe_read.index()), accum);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,smem_pipe_read.index()), tCrB(_,_,_,smem_pipe_read.index()), accum);
|
||||
warpgroup_commit_batch();
|
||||
|
||||
/// Wait on the GMMA barrier for K_PIPE_MMAS (or fewer) outstanding to ensure smem_pipe_write is consumed
|
||||
|
||||
@@ -173,21 +173,24 @@ struct CollectiveMma<
|
||||
// Device side kernel params
|
||||
struct Params {
|
||||
// Assumption: StrideA is congruent with Problem_MK
|
||||
using TMA_A = decltype(make_tma_copy(
|
||||
using TMA_A = decltype(make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(static_cast<InternalElementA const*>(nullptr), repeat_like(StrideA{}, int32_t(0)), StrideA{}),
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{}))); // mcast along N mode for this M load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
// Assumption: StrideB is congruent with Problem_NK
|
||||
using TMA_B = decltype(make_tma_copy(
|
||||
using TMA_B = decltype(make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(static_cast<InternalElementB const*>(nullptr), repeat_like(StrideB{}, int32_t(0)), StrideB{}),
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{}))); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
TMA_A tma_load_a;
|
||||
TMA_B tma_load_b;
|
||||
uint32_t tma_transaction_bytes = TmaTransactionBytes;
|
||||
uint32_t tma_transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t tma_transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -208,26 +211,34 @@ struct CollectiveMma<
|
||||
|
||||
Tensor tensor_a = make_tensor(ptr_A, make_layout(make_shape(M,K,L), args.dA));
|
||||
Tensor tensor_b = make_tensor(ptr_B, make_layout(make_shape(N,K,L), args.dB));
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy(
|
||||
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
tensor_a,
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{})); // mcast along N mode for this M load, if any
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy(
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
tensor_b,
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{})); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
uint32_t transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
uint32_t transaction_bytes = transaction_bytes_mk + transaction_bytes_nk;
|
||||
|
||||
return {
|
||||
tma_load_a,
|
||||
tma_load_b
|
||||
tma_load_b,
|
||||
transaction_bytes,
|
||||
transaction_bytes_mk,
|
||||
transaction_bytes_nk
|
||||
};
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -249,9 +260,11 @@ struct CollectiveMma<
|
||||
|
||||
static constexpr int K_PIPE_MAX = DispatchPolicy::Stages;
|
||||
static constexpr int K_PIPE_MMAS = 1;
|
||||
static constexpr uint32_t TmaTransactionBytes =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<ElementA>::value))+
|
||||
static constexpr uint32_t TmaTransactionBytesMK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<ElementA>::value));
|
||||
static constexpr uint32_t TmaTransactionBytesNK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * static_cast<uint32_t>(sizeof_bits<ElementB>::value));
|
||||
static constexpr uint32_t TmaTransactionBytes = TmaTransactionBytesMK + TmaTransactionBytesNK;
|
||||
|
||||
/// Issue Tma Descriptor Prefetch -- ideally from a single thread for best performance
|
||||
CUTLASS_DEVICE
|
||||
@@ -294,7 +307,7 @@ struct CollectiveMma<
|
||||
CUTLASS_DEVICE void
|
||||
load(
|
||||
Params const& mainloop_params,
|
||||
MainloopPipeline pipeline,
|
||||
MainloopPipeline pipeline,
|
||||
PipelineState smem_pipe_write,
|
||||
cute::tuple<TensorA, TensorB> const& load_inputs,
|
||||
BlockCoord const& blk_coord,
|
||||
@@ -354,8 +367,7 @@ struct CollectiveMma<
|
||||
|
||||
// Mainloop
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count)
|
||||
{
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
// LOCK smem_pipe_write for _writing_
|
||||
pipeline.producer_acquire(smem_pipe_write);
|
||||
|
||||
@@ -422,8 +434,22 @@ struct CollectiveMma<
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::ALayout{}) == 0 and
|
||||
stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::ALayout{}) == NumThreadsPerWarpGroup and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsA = thread_mma.partition_A(sA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
@@ -450,12 +476,9 @@ struct CollectiveMma<
|
||||
|
||||
// Prologue GMMAs
|
||||
int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
|
||||
assert(k_tile_count >= 1);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count; k_tile_prologue > 0; --k_tile_prologue)
|
||||
{
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
@@ -463,6 +486,7 @@ struct CollectiveMma<
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
warpgroup_arrive();
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
// Unroll the K mode manually to set scale D to 1
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
@@ -476,6 +500,25 @@ struct CollectiveMma<
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_tile_prologue = prologue_mma_count - 1; k_tile_prologue > 0; --k_tile_prologue)
|
||||
{
|
||||
// WAIT on smem_pipe_read until its data are available (phase bit flips from rdPhaseBit value)
|
||||
auto barrier_token = pipeline.consumer_try_wait(smem_pipe_read);
|
||||
pipeline.consumer_wait(smem_pipe_read, barrier_token);
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
warpgroup_arrive();
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum);
|
||||
warpgroup_commit_batch();
|
||||
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accum);
|
||||
// Mainloop GMMAs
|
||||
k_tile_count -= prologue_mma_count;
|
||||
@@ -494,13 +537,8 @@ struct CollectiveMma<
|
||||
int read_stage = smem_pipe_read.index();
|
||||
warpgroup_fence_operand(accum);
|
||||
warpgroup_arrive();
|
||||
// Unroll the K mode manually to set scale D to 1
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accum);
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
// (V,M,K) x (V,N,K) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,_,read_stage), tCrB(_,_,_,read_stage), accum);
|
||||
warpgroup_commit_batch();
|
||||
|
||||
/// Wait on the GMMA barrier for K_PIPE_MMAS (or fewer) outstanding to ensure smem_pipe_write is consumed
|
||||
|
||||
@@ -167,21 +167,24 @@ struct CollectiveMma<
|
||||
// Device side kernel params
|
||||
struct Params {
|
||||
// Assumption: StrideA is congruent with Problem_MK
|
||||
using TMA_A = decltype(make_tma_copy(
|
||||
using TMA_A = decltype(make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(static_cast<ElementA const*>(nullptr), repeat_like(StrideA{}, int32_t(0)), StrideA{}),
|
||||
SmemLayoutA{}(_,_,0),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{}))); // mcast along N mode for this M load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
// Assumption: StrideB is congruent with Problem_NK
|
||||
using TMA_B = decltype(make_tma_copy(
|
||||
using TMA_B = decltype(make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(static_cast<ElementB const*>(nullptr), repeat_like(StrideB{}, int32_t(0)), StrideB{}),
|
||||
SmemLayoutB{}(_,_,0),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{}))); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{}));
|
||||
TMA_A tma_load_a;
|
||||
TMA_B tma_load_b;
|
||||
uint32_t tma_transaction_bytes = TmaTransactionBytes;
|
||||
uint32_t tma_transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t tma_transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
uint32_t mma_promotion_interval = 4;
|
||||
};
|
||||
|
||||
@@ -203,27 +206,34 @@ struct CollectiveMma<
|
||||
|
||||
Tensor tensor_a = make_tensor(ptr_A, make_layout(make_shape(M,K,L), args.dA));
|
||||
Tensor tensor_b = make_tensor(ptr_B, make_layout(make_shape(N,K,L), args.dB));
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy(
|
||||
typename Params::TMA_A tma_load_a = make_tma_copy_A_sm90(
|
||||
GmemTiledCopyA{},
|
||||
tensor_a,
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<1>(ClusterShape{})); // mcast along N mode for this M load, if any
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy(
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
typename Params::TMA_B tma_load_b = make_tma_copy_B_sm90(
|
||||
GmemTiledCopyB{},
|
||||
tensor_b,
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
size<0>(ClusterShape{})); // mcast along M mode for this N load, if any
|
||||
TileShape{},
|
||||
ClusterShape{});
|
||||
uint32_t transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
uint32_t transaction_bytes = transaction_bytes_mk + transaction_bytes_nk;
|
||||
|
||||
return {
|
||||
tma_load_a,
|
||||
tma_load_b,
|
||||
transaction_bytes,
|
||||
transaction_bytes_mk,
|
||||
transaction_bytes_nk,
|
||||
args.mma_promotion_interval
|
||||
};
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
CUTLASS_HOST_DEVICE static bool
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
@@ -247,9 +257,11 @@ struct CollectiveMma<
|
||||
|
||||
static constexpr int K_PIPE_MAX = DispatchPolicy::Stages;
|
||||
static constexpr int K_PIPE_MMAS = 1;
|
||||
static constexpr uint32_t TmaTransactionBytes =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<ElementA>::value))+
|
||||
static constexpr uint32_t TmaTransactionBytesMK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<ElementA>::value));
|
||||
static constexpr uint32_t TmaTransactionBytesNK =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * static_cast<uint32_t>(sizeof_bits<ElementB>::value));
|
||||
static constexpr uint32_t TmaTransactionBytes = TmaTransactionBytesMK + TmaTransactionBytesNK;
|
||||
|
||||
/// Issue Tma Descriptor Prefetch -- ideally from a single thread for best performance
|
||||
CUTLASS_DEVICE
|
||||
@@ -321,8 +333,8 @@ struct CollectiveMma<
|
||||
|
||||
// Partition the inputs based on the current block coordinates.
|
||||
auto [m_coord, n_coord, k_coord, l_coord] = blk_coord;
|
||||
Tensor gA = gA_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gB = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
Tensor gA = gA_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gB = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
|
||||
// Applies the mapping from block_tma_a
|
||||
Tensor tAgA = block_tma_a.partition_S(gA); // (TMA,TMA_M,TMA_K,k)
|
||||
@@ -352,8 +364,7 @@ struct CollectiveMma<
|
||||
|
||||
// Mainloop
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count)
|
||||
{
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
// LOCK smem_pipe_write for _writing_
|
||||
pipeline.producer_acquire(smem_pipe_write);
|
||||
|
||||
@@ -422,9 +433,23 @@ struct CollectiveMma<
|
||||
//
|
||||
// Define C accumulators and A/B partitioning
|
||||
//
|
||||
|
||||
// Layout of warp group to thread mapping
|
||||
|
||||
static_assert(stride<0>(typename TiledMma::ALayout{}) == 0 and
|
||||
stride<0>(typename TiledMma::BLayout{}) == 0 and
|
||||
size<0>(typename TiledMma::ALayout{}) == NumThreadsPerWarpGroup and
|
||||
size<0>(typename TiledMma::BLayout{}) == NumThreadsPerWarpGroup,
|
||||
"Stride of the first mode must be 0 and the size of the mode must be NumThreadsPerWarpGroup");
|
||||
|
||||
constexpr int MmaWarpGroups = size(TiledMma{}) / NumThreadsPerWarpGroup;
|
||||
Layout warp_group_thread_layout = make_layout(Int<MmaWarpGroups>{},
|
||||
Int<NumThreadsPerWarpGroup>{});
|
||||
|
||||
int warp_group_idx = __shfl_sync(0xFFFFFFFF, thread_idx / NumThreadsPerWarpGroup, 0);
|
||||
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsA = thread_mma.partition_A(sA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCsB = thread_mma.partition_B(sB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
/*! \file
|
||||
\brief
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/arch/mma.h"
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/arch/arch.h"
|
||||
#include "cutlass/device_kernel.h"
|
||||
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/threadblock/threadblock_swizzle.h"
|
||||
#include "cutlass/gemm/kernel/gemm_sparse_universal.h"
|
||||
|
||||
#include "cutlass/gemm/kernel/default_gemm_sparse_universal.h"
|
||||
#include "cutlass/gemm/device/default_gemm_configuration.h"
|
||||
#include "cutlass/gemm/device/gemm_universal_base.h"
|
||||
|
||||
#include "cutlass/layout/permute.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace device {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*!
|
||||
GemmSparseUniversal is a stateful, reusable Sparse GEMM handle. Once initialized for a given GEMM computation
|
||||
(problem geometry and data references), it can be reused across different GEMM problems having the
|
||||
geometry. (Once initialized, details regarding problem geometry and references to workspace memory
|
||||
cannot be updated.)
|
||||
|
||||
The universal GEMM accommodates serial reductions, parallel reductions, batched strided, and
|
||||
batched array variants.
|
||||
*/
|
||||
template <
|
||||
/// Element type for A matrix operand
|
||||
typename ElementA_,
|
||||
/// Layout type for A matrix operand
|
||||
typename LayoutA_,
|
||||
/// Element type for B matrix operand
|
||||
typename ElementB_,
|
||||
/// Layout type for B matrix operand
|
||||
typename LayoutB_,
|
||||
/// Element type for C and D matrix operands
|
||||
typename ElementC_,
|
||||
/// Layout type for C and D matrix operands
|
||||
typename LayoutC_,
|
||||
/// Element type for internal accumulation
|
||||
typename ElementAccumulator_ = ElementC_,
|
||||
/// Operator class tag
|
||||
typename OperatorClass_ = arch::OpClassTensorOp,
|
||||
/// Tag indicating architecture to tune for. This is the minimum SM that
|
||||
/// supports the intended feature. The device kernel can be built
|
||||
/// targeting any SM larger than this number.
|
||||
typename ArchTag_ = arch::Sm80,
|
||||
/// Threadblock-level tile size (concept: GemmShape)
|
||||
typename ThreadblockShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::ThreadblockShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename WarpShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::WarpShape,
|
||||
/// Instruction-level tile size (concept: GemmShape)
|
||||
typename InstructionShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::InstructionShape,
|
||||
/// Epilogue output operator
|
||||
typename EpilogueOutputOp_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::EpilogueOutputOp,
|
||||
/// Threadblock-level swizzling operator
|
||||
typename ThreadblockSwizzle_ = threadblock::GemmIdentityThreadblockSwizzle<>,
|
||||
/// Number of stages used in the pipelined mainloop
|
||||
int Stages =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kStages,
|
||||
/// Access granularity of A matrix in units of elements
|
||||
int AlignmentA =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kAlignmentA,
|
||||
/// Access granularity of B matrix in units of elements
|
||||
int AlignmentB =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kAlignmentB,
|
||||
/// Operation performed by GEMM
|
||||
typename Operator_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::Operator>
|
||||
class GemmSparseUniversal :
|
||||
public GemmUniversalBase<
|
||||
typename kernel::DefaultGemmSparseUniversal<
|
||||
ElementA_,
|
||||
LayoutA_,
|
||||
AlignmentA,
|
||||
ElementB_,
|
||||
LayoutB_,
|
||||
AlignmentB,
|
||||
ElementC_,
|
||||
LayoutC_,
|
||||
ElementAccumulator_,
|
||||
OperatorClass_,
|
||||
ArchTag_,
|
||||
ThreadblockShape_,
|
||||
WarpShape_,
|
||||
InstructionShape_,
|
||||
EpilogueOutputOp_,
|
||||
ThreadblockSwizzle_,
|
||||
Stages,
|
||||
Operator_
|
||||
>::GemmKernel
|
||||
> {
|
||||
|
||||
public:
|
||||
|
||||
static_assert((platform::is_same<LayoutC_, layout::RowMajor>::value),
|
||||
"Epilogue of Ampere sparse GEMM must be row major for now.");
|
||||
|
||||
using ElementAccumulator = ElementAccumulator_;
|
||||
using OperatorClass = OperatorClass_;
|
||||
using ArchTag = ArchTag_;
|
||||
using ThreadblockShape = ThreadblockShape_;
|
||||
using WarpShape = WarpShape_;
|
||||
using InstructionShape = InstructionShape_;
|
||||
using EpilogueOutputOp = EpilogueOutputOp_;
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
using Operator = Operator_;
|
||||
static int const kStages = Stages;
|
||||
static int const kAlignmentA = AlignmentA;
|
||||
static int const kAlignmentB = AlignmentB;
|
||||
static int const kAlignmentC = EpilogueOutputOp::kCount;
|
||||
|
||||
using Base = GemmUniversalBase<
|
||||
typename kernel::DefaultGemmSparseUniversal<
|
||||
ElementA_,
|
||||
LayoutA_,
|
||||
AlignmentA,
|
||||
ElementB_,
|
||||
LayoutB_,
|
||||
AlignmentB,
|
||||
ElementC_,
|
||||
LayoutC_,
|
||||
ElementAccumulator_,
|
||||
OperatorClass_,
|
||||
ArchTag_,
|
||||
ThreadblockShape_,
|
||||
WarpShape_,
|
||||
InstructionShape_,
|
||||
EpilogueOutputOp_,
|
||||
ThreadblockSwizzle_,
|
||||
Stages,
|
||||
Operator_
|
||||
>::GemmKernel
|
||||
>;
|
||||
|
||||
using Arguments = typename Base::Arguments;
|
||||
using GemmKernel = typename Base::GemmKernel;
|
||||
|
||||
using ElementE = typename GemmKernel::ElementE;
|
||||
|
||||
using LayoutE = typename GemmKernel::LayoutE;
|
||||
|
||||
static int const kAlignmentE = 128 / sizeof_bits<ElementE>::value;
|
||||
|
||||
static int const kSparse = GemmKernel::kSparse;
|
||||
static int const kMetaSizeInBits = GemmKernel::kMetaSizeInBits;
|
||||
static int const kElementsPerElementE = GemmKernel::kElementsPerElementE;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace device
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -0,0 +1,202 @@
|
||||
/***************************************************************************************************
|
||||
* 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
/*! \file
|
||||
\brief
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/arch/mma.h"
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/arch/arch.h"
|
||||
#include "cutlass/device_kernel.h"
|
||||
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/threadblock/threadblock_swizzle.h"
|
||||
#include "cutlass/gemm/kernel/gemm_sparse_universal.h"
|
||||
|
||||
#include "cutlass/gemm/kernel/default_gemm_sparse_universal_with_absmax.h"
|
||||
#include "cutlass/gemm/device/default_gemm_configuration.h"
|
||||
#include "cutlass/gemm/device/gemm_universal_base.h"
|
||||
|
||||
#include "cutlass/layout/permute.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace device {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
/// Element type for A matrix operand
|
||||
typename ElementA_,
|
||||
/// Layout type for A matrix operand
|
||||
typename LayoutA_,
|
||||
/// Element type for B matrix operand
|
||||
typename ElementB_,
|
||||
/// Layout type for B matrix operand
|
||||
typename LayoutB_,
|
||||
/// Element type for C and D matrix operands
|
||||
typename ElementC_,
|
||||
/// Layout type for C and D matrix operands
|
||||
typename LayoutC_,
|
||||
/// Element type for internal accumulation
|
||||
typename ElementAccumulator_ = ElementC_,
|
||||
/// Operator class tag
|
||||
typename OperatorClass_ = arch::OpClassTensorOp,
|
||||
/// Tag indicating architecture to tune for. This is the minimum SM that
|
||||
/// supports the intended feature. The device kernel can be built
|
||||
/// targeting any SM larger than this number.
|
||||
typename ArchTag_ = arch::Sm80,
|
||||
/// Threadblock-level tile size (concept: GemmShape)
|
||||
typename ThreadblockShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::ThreadblockShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename WarpShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::WarpShape,
|
||||
/// Instruction-level tile size (concept: GemmShape)
|
||||
typename InstructionShape_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::InstructionShape,
|
||||
/// Epilogue output operator
|
||||
typename EpilogueOutputOp_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::EpilogueOutputOp,
|
||||
/// Threadblock-level swizzling operator
|
||||
typename ThreadblockSwizzle_ = threadblock::GemmIdentityThreadblockSwizzle<>,
|
||||
/// Number of stages used in the pipelined mainloop
|
||||
int Stages =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kStages,
|
||||
/// Access granularity of A matrix in units of elements
|
||||
int AlignmentA =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kAlignmentA,
|
||||
/// Access granularity of B matrix in units of elements
|
||||
int AlignmentB =
|
||||
DefaultGemmConfiguration<OperatorClass_, ArchTag_, ElementA_, ElementB_,
|
||||
ElementC_, ElementAccumulator_>::kAlignmentB,
|
||||
/// Operation performed by GEMM
|
||||
typename Operator_ = typename DefaultGemmConfiguration<
|
||||
OperatorClass_, ArchTag_, ElementA_, ElementB_, ElementC_,
|
||||
ElementAccumulator_>::Operator>
|
||||
class GemmSparseUniversalWithAbsmax :
|
||||
public GemmUniversalBase<
|
||||
typename kernel::DefaultGemmSparseUniversalWithAbsmax<
|
||||
ElementA_,
|
||||
LayoutA_,
|
||||
AlignmentA,
|
||||
ElementB_,
|
||||
LayoutB_,
|
||||
AlignmentB,
|
||||
ElementC_,
|
||||
LayoutC_,
|
||||
ElementAccumulator_,
|
||||
OperatorClass_,
|
||||
ArchTag_,
|
||||
ThreadblockShape_,
|
||||
WarpShape_,
|
||||
InstructionShape_,
|
||||
EpilogueOutputOp_,
|
||||
ThreadblockSwizzle_,
|
||||
Stages,
|
||||
Operator_
|
||||
>::GemmKernel
|
||||
> {
|
||||
|
||||
public:
|
||||
|
||||
static_assert((platform::is_same<LayoutC_, layout::RowMajor>::value),
|
||||
"Epilogue of Ada sparse GEMM must be row major for now.");
|
||||
|
||||
using ElementAccumulator = ElementAccumulator_;
|
||||
using OperatorClass = OperatorClass_;
|
||||
using ArchTag = ArchTag_;
|
||||
using ThreadblockShape = ThreadblockShape_;
|
||||
using WarpShape = WarpShape_;
|
||||
using InstructionShape = InstructionShape_;
|
||||
using EpilogueOutputOp = EpilogueOutputOp_;
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
using Operator = Operator_;
|
||||
static int const kStages = Stages;
|
||||
static int const kAlignmentA = AlignmentA;
|
||||
static int const kAlignmentB = AlignmentB;
|
||||
static int const kAlignmentC = EpilogueOutputOp::kCount;
|
||||
|
||||
using Base = GemmUniversalBase<
|
||||
typename kernel::DefaultGemmSparseUniversalWithAbsmax<
|
||||
ElementA_,
|
||||
LayoutA_,
|
||||
AlignmentA,
|
||||
ElementB_,
|
||||
LayoutB_,
|
||||
AlignmentB,
|
||||
ElementC_,
|
||||
LayoutC_,
|
||||
ElementAccumulator_,
|
||||
OperatorClass_,
|
||||
ArchTag_,
|
||||
ThreadblockShape_,
|
||||
WarpShape_,
|
||||
InstructionShape_,
|
||||
EpilogueOutputOp_,
|
||||
ThreadblockSwizzle_,
|
||||
Stages,
|
||||
Operator_
|
||||
>::GemmKernel
|
||||
>;
|
||||
|
||||
using Arguments = typename Base::Arguments;
|
||||
using GemmKernel = typename Base::GemmKernel;
|
||||
|
||||
using ElementE = typename GemmKernel::ElementE;
|
||||
|
||||
using LayoutE = typename GemmKernel::LayoutE;
|
||||
|
||||
static int const kAlignmentE = 128 / sizeof_bits<ElementE>::value;
|
||||
|
||||
static int const kSparse = GemmKernel::kSparse;
|
||||
static int const kMetaSizeInBits = GemmKernel::kMetaSizeInBits;
|
||||
static int const kElementsPerElementE = GemmKernel::kElementsPerElementE;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace device
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -338,7 +338,8 @@ public:
|
||||
static Status
|
||||
run(Params& params,
|
||||
cudaStream_t stream = nullptr,
|
||||
CudaHostAdapter *cuda_adapter = nullptr) {
|
||||
CudaHostAdapter *cuda_adapter = nullptr,
|
||||
bool launch_with_pdl = false) {
|
||||
CUTLASS_TRACE_HOST("GemmUniversal::run()");
|
||||
dim3 const block = GemmKernel::get_block_shape();
|
||||
dim3 const grid = get_grid_shape(params);
|
||||
@@ -361,6 +362,11 @@ public:
|
||||
CUTLASS_ASSERT(cuda_adapter);
|
||||
if (cuda_adapter) {
|
||||
|
||||
if (launch_with_pdl) {
|
||||
CUTLASS_TRACE_HOST(
|
||||
"GemmUniversal::run() does not support launching with PDL and a custom cuda adapter.");
|
||||
return Status::kErrorInternal;
|
||||
}
|
||||
launch_result = cuda_adapter->launch(grid,
|
||||
cluster,
|
||||
block,
|
||||
@@ -378,7 +384,7 @@ public:
|
||||
void const* kernel = (void const*) device_kernel<GemmKernel>;
|
||||
if constexpr (GemmKernel::ArchTag::kMinComputeCapability == 90) {
|
||||
launch_result = ClusterLauncher::launch(
|
||||
grid, cluster, block, smem_size, stream, kernel, kernel_params);
|
||||
grid, cluster, block, smem_size, stream, kernel, kernel_params, launch_with_pdl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,12 +430,13 @@ public:
|
||||
Arguments const& args,
|
||||
void* workspace = nullptr,
|
||||
cudaStream_t stream = nullptr,
|
||||
CudaHostAdapter *cuda_adapter = nullptr
|
||||
CudaHostAdapter *cuda_adapter = nullptr,
|
||||
bool launch_with_pdl = false
|
||||
) {
|
||||
Status status = initialize(args, workspace, stream, cuda_adapter);
|
||||
|
||||
if (Status::kSuccess == status) {
|
||||
status = run(params_, stream, cuda_adapter);
|
||||
status = run(params_, stream, cuda_adapter, launch_with_pdl);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -440,20 +447,24 @@ public:
|
||||
Arguments const& args,
|
||||
void* workspace = nullptr,
|
||||
cudaStream_t stream = nullptr,
|
||||
CudaHostAdapter *cuda_adapter = nullptr) {
|
||||
return run(args, workspace, stream, cuda_adapter);
|
||||
CudaHostAdapter *cuda_adapter = nullptr,
|
||||
bool launch_with_pdl = false) {
|
||||
return run(args, workspace, stream, cuda_adapter, launch_with_pdl);
|
||||
}
|
||||
|
||||
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
|
||||
Status
|
||||
run(cudaStream_t stream = nullptr, CudaHostAdapter *cuda_adapter = nullptr) {
|
||||
return run(params_, stream, cuda_adapter);
|
||||
run(
|
||||
cudaStream_t stream = nullptr,
|
||||
CudaHostAdapter *cuda_adapter = nullptr,
|
||||
bool launch_with_pdl = false) {
|
||||
return run(params_, stream, cuda_adapter, launch_with_pdl);
|
||||
}
|
||||
|
||||
/// Overload that allows a user to re-launch the same kernel without updating internal params struct.
|
||||
Status
|
||||
operator()(cudaStream_t stream = nullptr, CudaHostAdapter *cuda_adapter = nullptr) {
|
||||
return run(params_, stream, cuda_adapter);
|
||||
operator()(cudaStream_t stream = nullptr, CudaHostAdapter *cuda_adapter = nullptr, bool launch_with_pdl = false) {
|
||||
return run(params_, stream, cuda_adapter, launch_with_pdl);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
\brief The universal GEMM accommodates streamk, batched strided, and batched array variants.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__CUDACC_RTC__)
|
||||
@@ -271,12 +270,33 @@ public:
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversalBase::can_implement()");
|
||||
|
||||
dim3 grid = get_grid_shape(args, cuda_adapter);
|
||||
if (!kEnableCudaHostAdapter || cuda_adapter) {
|
||||
|
||||
dim3 grid = get_grid_shape(args, cuda_adapter);
|
||||
|
||||
if (!(grid.y <= std::numeric_limits<uint16_t>::max() &&
|
||||
grid.z <= std::numeric_limits<uint16_t>::max()))
|
||||
{
|
||||
return Status::kErrorInvalidProblem;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//
|
||||
// With a null host adapter, a conservative grid shape is computed and required to conform to CUDA grid
|
||||
// dimension limits.
|
||||
//
|
||||
|
||||
int64_t logicalGridM = (int64_t(args.problem_size.m()) + ThreadblockShape::kM - 1) / ThreadblockShape::kM;
|
||||
int64_t logicalGridN = (int64_t(args.problem_size.n()) + ThreadblockShape::kN - 1) / ThreadblockShape::kN;
|
||||
int32_t logicalGridL = args.batch_count;
|
||||
|
||||
if ((int64_t(std::numeric_limits<uint32_t>::max()) < logicalGridM) ||
|
||||
(int64_t(std::numeric_limits<uint16_t>::max()) < logicalGridN) ||
|
||||
(int32_t(std::numeric_limits<uint16_t>::max()) < logicalGridL)) {
|
||||
|
||||
return Status::kErrorInvalidProblem;
|
||||
}
|
||||
|
||||
if (!(grid.y <= std::numeric_limits<uint16_t>::max() &&
|
||||
grid.z <= std::numeric_limits<uint16_t>::max()))
|
||||
{
|
||||
return Status::kErrorInvalidProblem;
|
||||
}
|
||||
|
||||
return GemmKernel::can_implement(args);
|
||||
|
||||
@@ -68,6 +68,24 @@ enum class KernelInputTransformType {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace kernel::detail {
|
||||
|
||||
// Has_SwapAB<T>::value will be true only if:
|
||||
// class T has member SwapAB and T::SwapAB is true
|
||||
template <typename T, typename = void>
|
||||
struct Has_SwapAB { static constexpr bool value = false; };
|
||||
|
||||
template <typename T>
|
||||
struct Has_SwapAB <T, CUTE_STL_NAMESPACE::void_t<decltype(T::SwapAB)>>
|
||||
{ static constexpr bool value = T::SwapAB; };
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool Has_SwapAB_v = Has_SwapAB<T>::value;
|
||||
|
||||
} // namespace kernel::detail
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// Kernel schedule policies (the base class tags, one for each kernel layer file)
|
||||
//
|
||||
@@ -137,12 +155,15 @@ struct MainloopSm80CpAsyncUnpredicated {
|
||||
};
|
||||
|
||||
// n-buffer in smem (cp.async), pipelined with registers, with predicated gmem loads
|
||||
template<int Stages_>
|
||||
template<
|
||||
int Stages_,
|
||||
class ClusterShape_ = Shape<_1,_1,_1>
|
||||
>
|
||||
struct MainloopSm80CpAsync {
|
||||
constexpr static int Stages = Stages_;
|
||||
using ArchTag = arch::Sm80;
|
||||
using ArchTag = cute::conditional_t<(size(ClusterShape_{}) > 1), arch::Sm90, arch::Sm80>;
|
||||
using Schedule = KernelMultistage;
|
||||
using ClusterShape = Shape<_1,_1,_1>;
|
||||
using ClusterShape = ClusterShape_;
|
||||
};
|
||||
|
||||
// n-buffer in smem (cp.async), pipelined with Hopper GMMA, with predicated gmem loads, warp specialized dynamic schedule
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
Default kernel-level Sparse GEMM definitions combine threadblock-scoped matrix multiply-add with
|
||||
the appropriate threadblock-scoped epilogue.
|
||||
|
||||
Note, CUTLASS epilogues universally target row-major outputs. Column-major outputs are
|
||||
accommodated by exchanging A and B operands and assuming transposed layouts. Partial
|
||||
specializations here choose 'device::GemmTransposed' to implement this functionality.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
|
||||
#include "cutlass/gemm/kernel/gemm_sparse_universal.h"
|
||||
#include "cutlass/gemm/kernel/default_gemm_sparse.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// Real-valued GEMM kernels
|
||||
//
|
||||
|
||||
template <
|
||||
/// Element type for A matrix operand
|
||||
typename ElementA,
|
||||
/// Layout type for A matrix operand
|
||||
typename LayoutA,
|
||||
/// Access granularity of A matrix in units of elements
|
||||
int kAlignmentA,
|
||||
/// Element type for B matrix operand
|
||||
typename ElementB,
|
||||
/// Layout type for B matrix operand
|
||||
typename LayoutB,
|
||||
/// Access granularity of B matrix in units of elements
|
||||
int kAlignmentB,
|
||||
/// Element type for C and D matrix operands
|
||||
typename ElementC,
|
||||
/// Layout type for C and D matrix operands
|
||||
typename LayoutC,
|
||||
/// Element type for internal accumulation
|
||||
typename ElementAccumulator,
|
||||
/// Operator class tag
|
||||
typename OperatorClass,
|
||||
/// Tag indicating architecture to tune for
|
||||
typename ArchTag,
|
||||
/// Threadblock-level tile size (concept: GemmShape)
|
||||
typename ThreadblockShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename WarpShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename InstructionShape,
|
||||
/// Epilogue output operator
|
||||
typename EpilogueOutputOp,
|
||||
/// Threadblock-level swizzling operator
|
||||
typename ThreadblockSwizzle,
|
||||
/// Number of stages used in the pipelined mainloop
|
||||
int Stages,
|
||||
/// Operation performed by GEMM
|
||||
typename Operator
|
||||
>
|
||||
struct DefaultGemmSparseUniversal {
|
||||
|
||||
using DefaultGemmKernel = typename kernel::DefaultSparseGemm<
|
||||
ElementA,
|
||||
LayoutA,
|
||||
kAlignmentA,
|
||||
ElementB,
|
||||
LayoutB,
|
||||
kAlignmentB,
|
||||
ElementC,
|
||||
LayoutC,
|
||||
ElementAccumulator,
|
||||
OperatorClass,
|
||||
ArchTag,
|
||||
ThreadblockShape,
|
||||
WarpShape,
|
||||
InstructionShape,
|
||||
EpilogueOutputOp,
|
||||
ThreadblockSwizzle,
|
||||
Stages,
|
||||
true,
|
||||
Operator
|
||||
>::GemmKernel;
|
||||
|
||||
/// Select kernel by ThreadblockSwizzle's support for StreamkFeature
|
||||
using GemmKernel = kernel::GemmSparseUniversal<
|
||||
typename DefaultGemmKernel::Mma,
|
||||
typename DefaultGemmKernel::Epilogue,
|
||||
ThreadblockSwizzle>;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -0,0 +1,144 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
Default kernel-level Sparse GEMM definitions combine threadblock-scoped matrix multiply-add with
|
||||
the appropriate threadblock-scoped epilogue.
|
||||
|
||||
Note, CUTLASS epilogues universally target row-major outputs. Column-major outputs are
|
||||
accommodated by exchanging A and B operands and assuming transposed layouts. Partial
|
||||
specializations here choose 'device::GemmTransposed' to implement this functionality.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
|
||||
#include "cutlass/epilogue/threadblock/default_epilogue_with_absmax.h"
|
||||
#include "cutlass/gemm/kernel/gemm_sparse_universal_with_absmax.h"
|
||||
#include "cutlass/gemm/kernel/default_gemm_sparse.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// Real-valued GEMM kernels
|
||||
//
|
||||
|
||||
template <
|
||||
/// Element type for A matrix operand
|
||||
typename ElementA,
|
||||
/// Layout type for A matrix operand
|
||||
typename LayoutA,
|
||||
/// Access granularity of A matrix in units of elements
|
||||
int kAlignmentA,
|
||||
/// Element type for B matrix operand
|
||||
typename ElementB,
|
||||
/// Layout type for B matrix operand
|
||||
typename LayoutB,
|
||||
/// Access granularity of B matrix in units of elements
|
||||
int kAlignmentB,
|
||||
/// Element type for C and D matrix operands
|
||||
typename ElementC,
|
||||
/// Layout type for C and D matrix operands
|
||||
typename LayoutC,
|
||||
/// Element type for internal accumulation
|
||||
typename ElementAccumulator,
|
||||
/// Operator class tag
|
||||
typename OperatorClass,
|
||||
/// Tag indicating architecture to tune for
|
||||
typename ArchTag,
|
||||
/// Threadblock-level tile size (concept: GemmShape)
|
||||
typename ThreadblockShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename WarpShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename InstructionShape,
|
||||
/// Epilogue output operator
|
||||
typename EpilogueOutputOp,
|
||||
/// Threadblock-level swizzling operator
|
||||
typename ThreadblockSwizzle,
|
||||
/// Number of stages used in the pipelined mainloop
|
||||
int Stages,
|
||||
/// Operation performed by GEMM
|
||||
typename Operator
|
||||
>
|
||||
struct DefaultGemmSparseUniversalWithAbsmax {
|
||||
|
||||
using GemmBase = typename DefaultSparseGemm<
|
||||
ElementA, LayoutA, kAlignmentA,
|
||||
ElementB, LayoutB, kAlignmentB,
|
||||
ElementC, LayoutC, ElementAccumulator,
|
||||
OperatorClass,
|
||||
ArchTag,
|
||||
ThreadblockShape,
|
||||
WarpShape,
|
||||
InstructionShape,
|
||||
EpilogueOutputOp,
|
||||
ThreadblockSwizzle,
|
||||
Stages,
|
||||
false, // SplitKSerial
|
||||
Operator
|
||||
>::GemmKernel;
|
||||
|
||||
using Epilogue = typename cutlass::epilogue::threadblock::DefaultEpilogueWithAbsMax<
|
||||
typename GemmBase::Epilogue::Shape,
|
||||
typename GemmBase::Epilogue::WarpMmaOperator,
|
||||
GemmBase::Epilogue::kPartitionsK,
|
||||
ElementC,
|
||||
typename EpilogueOutputOp::ElementAuxOutput,
|
||||
ElementC,
|
||||
EpilogueOutputOp,
|
||||
GemmBase::Epilogue::kElementsPerAccess
|
||||
>::Epilogue;
|
||||
|
||||
using GemmKernel = kernel::GemmSparseUniversalWithAbsmax<
|
||||
typename GemmBase::Mma, Epilogue, ThreadblockSwizzle>;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -167,7 +167,7 @@ struct DefaultSparseGemmWithVisitor<ElementA, LayoutA, kAlignmentA, ElementB, La
|
||||
ThreadblockShape, WarpShape, InstructionShape, Stages,
|
||||
Operator>::ThreadblockMma;
|
||||
|
||||
static constexpr int kAlignmentC = 128 / sizeof_bits<ElementC>::value;;
|
||||
static constexpr int kAlignmentC = 128 / sizeof_bits<ElementC>::value;
|
||||
using ElementEpilogue = ElementAccumulator;
|
||||
|
||||
static const int kPartitionsK = ThreadblockShape::kK / WarpShape::kK;
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
\brief
|
||||
Default kernel-level GEMM definitions combine threadblock-scoped matrix multiply-add with
|
||||
the appropriate threadblock-scoped epilogue.
|
||||
|
||||
|
||||
Note, CUTLASS epilogues universally target row-major outputs. Column-major outputs are
|
||||
accommodated by exchanging A and B operands and assuming transposed layouts. Partial
|
||||
specializations here choose 'device::GemmTransposed' to implement this functionality.
|
||||
|
||||
@@ -0,0 +1,804 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
|
||||
#include "cutlass/arch/arch.h"
|
||||
#include "cutlass/fast_math.h"
|
||||
#include "cutlass/matrix_coord.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/semaphore.h"
|
||||
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/kernel/params_universal_base.h"
|
||||
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
namespace detail {
|
||||
|
||||
template <
|
||||
typename LayoutA,
|
||||
typename LayoutB,
|
||||
typename LayoutC,
|
||||
typename LayoutE
|
||||
>
|
||||
struct SparseUniversalArgumentsBase : UniversalArgumentsBase {
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
void const * ptr_A;
|
||||
void const * ptr_B;
|
||||
void const * ptr_C;
|
||||
void * ptr_D;
|
||||
void const * ptr_E;
|
||||
|
||||
int64_t batch_stride_A;
|
||||
int64_t batch_stride_B;
|
||||
int64_t batch_stride_C;
|
||||
int64_t batch_stride_E;
|
||||
|
||||
typename LayoutA::Stride::LongIndex lda;
|
||||
typename LayoutB::Stride::LongIndex ldb;
|
||||
typename LayoutC::Stride::LongIndex ldc;
|
||||
typename LayoutC::Stride::LongIndex ldd;
|
||||
typename LayoutE::Stride::LongIndex lde;
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
SparseUniversalArgumentsBase():
|
||||
ptr_A(nullptr), ptr_B(nullptr), ptr_C(nullptr), ptr_D(nullptr), ptr_E(nullptr)
|
||||
{}
|
||||
|
||||
/// constructs an arguments structure
|
||||
SparseUniversalArgumentsBase(
|
||||
GemmUniversalMode mode,
|
||||
GemmCoord problem_size,
|
||||
int batch_count,
|
||||
void const * ptr_A,
|
||||
void const * ptr_B,
|
||||
void const * ptr_C,
|
||||
void * ptr_D,
|
||||
void const * ptr_E,
|
||||
int64_t batch_stride_A,
|
||||
int64_t batch_stride_B,
|
||||
int64_t batch_stride_C,
|
||||
int64_t batch_stride_D,
|
||||
int64_t batch_stride_E,
|
||||
typename LayoutA::Stride::LongIndex lda,
|
||||
typename LayoutB::Stride::LongIndex ldb,
|
||||
typename LayoutC::Stride::LongIndex ldc,
|
||||
typename LayoutC::Stride::LongIndex ldd,
|
||||
typename LayoutC::Stride::LongIndex lde)
|
||||
:
|
||||
UniversalArgumentsBase(mode, problem_size, batch_count, batch_stride_D),
|
||||
ptr_A(ptr_A), ptr_B(ptr_B), ptr_C(ptr_C), ptr_D(ptr_D), ptr_E(ptr_E),
|
||||
batch_stride_A(batch_stride_A), batch_stride_B(batch_stride_B), batch_stride_C(batch_stride_C),
|
||||
batch_stride_E(batch_stride_E),
|
||||
lda(lda), ldb(ldb), ldc(ldc), ldd(ldd), lde(lde)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("SparseUniversalArgumentsBase::Arguments() - problem_size: " << problem_size);
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename Mma,
|
||||
typename Epilogue,
|
||||
typename Arguments,
|
||||
typename ThreadblockSwizzle,
|
||||
typename ThreadblockShape,
|
||||
typename ElementA,
|
||||
typename ElementB,
|
||||
typename ElementC,
|
||||
typename LayoutA,
|
||||
typename LayoutB
|
||||
>
|
||||
struct SparseUniversalParamsBase : UniversalParamsBase<
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB> {
|
||||
using ParamsBase = UniversalParamsBase<
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>;
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
typename Mma::IteratorA::Params params_A;
|
||||
typename Mma::IteratorB::Params params_B;
|
||||
typename Epilogue::OutputTileIterator::Params params_C;
|
||||
typename Epilogue::OutputTileIterator::Params params_D;
|
||||
typename Mma::IteratorE::Params params_E;
|
||||
|
||||
void * ptr_A;
|
||||
void * ptr_B;
|
||||
void * ptr_C;
|
||||
void * ptr_D;
|
||||
void * ptr_E;
|
||||
|
||||
int64_t batch_stride_A;
|
||||
int64_t batch_stride_B;
|
||||
int64_t batch_stride_C;
|
||||
int64_t batch_stride_E;
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
SparseUniversalParamsBase() = default;
|
||||
|
||||
/// Constructor
|
||||
SparseUniversalParamsBase(
|
||||
Arguments const &args, /// GEMM application arguments
|
||||
int device_sms, /// Number of SMs on the device
|
||||
int sm_occupancy) /// Kernel SM occupancy (in thread blocks)
|
||||
:
|
||||
ParamsBase(args, device_sms, sm_occupancy),
|
||||
params_A(args.lda),
|
||||
params_B(args.ldb),
|
||||
params_C(args.ldc),
|
||||
params_D(args.ldd),
|
||||
params_E(args.lde),
|
||||
ptr_A(const_cast<void *>(args.ptr_A)),
|
||||
ptr_B(const_cast<void *>(args.ptr_B)),
|
||||
ptr_C(const_cast<void *>(args.ptr_C)),
|
||||
ptr_D(args.ptr_D),
|
||||
ptr_E(const_cast<void *>(args.ptr_E)),
|
||||
batch_stride_A(args.batch_stride_A),
|
||||
batch_stride_B(args.batch_stride_B),
|
||||
batch_stride_C(args.batch_stride_C),
|
||||
batch_stride_E(args.batch_stride_E)
|
||||
{}
|
||||
|
||||
/// Lightweight update given a subset of arguments.
|
||||
void update(Arguments const &args)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("SparseUniversalParamsBase::update()");
|
||||
|
||||
// Update input/output pointers
|
||||
this->ptr_A = const_cast<void *>(args.ptr_A);
|
||||
this->ptr_B = const_cast<void *>(args.ptr_B);
|
||||
this->ptr_C = const_cast<void *>(args.ptr_C);
|
||||
this->ptr_D = args.ptr_D;
|
||||
this->ptr_E = const_cast<void *>(args.ptr_E);
|
||||
|
||||
this->batch_stride_A = args.batch_stride_A;
|
||||
this->batch_stride_B = args.batch_stride_B;
|
||||
this->batch_stride_C = args.batch_stride_C;
|
||||
this->batch_stride_D = args.batch_stride_D;
|
||||
this->batch_stride_E = args.batch_stride_E;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
typename Mma_, ///! Threadblock-scoped matrix multiply-accumulate
|
||||
typename Epilogue_, ///! Epilogue
|
||||
typename ThreadblockSwizzle_ ///! Threadblock swizzling function
|
||||
>
|
||||
class GemmSparseUniversal {
|
||||
public:
|
||||
|
||||
using Mma = Mma_;
|
||||
using Epilogue = Epilogue_;
|
||||
using EpilogueOutputOp = typename Epilogue::OutputOp;
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
|
||||
static int const kSparse = Mma::kSparse;
|
||||
static int const kMetaSizeInBits = Mma::kMetaSizeInBits;
|
||||
static int const kMaxID2 = Mma::kMaxID2;
|
||||
static int const kElementsPerElementE = Mma::kElementsPerElementE;
|
||||
|
||||
using ElementE = typename Mma::ElementE;
|
||||
using LayoutE = typename Mma::LayoutE;
|
||||
|
||||
using ElementA = typename Mma::IteratorA::Element;
|
||||
using LayoutA = typename Mma::IteratorA::Layout;
|
||||
using ElementB = typename Mma::IteratorB::Element;
|
||||
using LayoutB = typename Mma::IteratorB::Layout;
|
||||
using ElementC = typename Epilogue::OutputTileIterator::Element;
|
||||
using LayoutC = typename Epilogue::OutputTileIterator::Layout;
|
||||
|
||||
static ComplexTransform const kTransformA = Mma::kTransformA;
|
||||
static ComplexTransform const kTransformB = Mma::kTransformB;
|
||||
using Operator = typename Mma::Operator;
|
||||
|
||||
using OperatorClass = typename Mma::Operator::OperatorClass;
|
||||
using ThreadblockShape = typename Mma::Shape;
|
||||
using WarpShape = typename Mma::Operator::Shape;
|
||||
using InstructionShape = typename Mma::Policy::Operator::InstructionShape;
|
||||
using ArchTag = typename Mma::ArchTag;
|
||||
|
||||
static int const kStages = Mma::kStages;
|
||||
static int const kAlignmentA = Mma::IteratorA::AccessType::kElements;
|
||||
static int const kAlignmentB = Mma::IteratorB::AccessType::kElements;
|
||||
static int const kAlignmentC = Epilogue::OutputTileIterator::kElementsPerAccess;
|
||||
|
||||
/// Warp count (concept: GemmShape)
|
||||
using WarpCount = typename Mma::WarpCount;
|
||||
static int const kThreadCount = 32 * WarpCount::kCount;
|
||||
|
||||
/// Split-K preserves splits that are 128b aligned
|
||||
static int const kSplitKAlignment = const_max(128 / sizeof_bits<ElementA>::value, 128 / sizeof_bits<ElementB>::value);
|
||||
|
||||
//
|
||||
// Structures
|
||||
//
|
||||
|
||||
/// Argument structure
|
||||
struct Arguments : detail::SparseUniversalArgumentsBase<
|
||||
LayoutA,
|
||||
LayoutB,
|
||||
LayoutC,
|
||||
LayoutE
|
||||
> {
|
||||
using Base = detail::SparseUniversalArgumentsBase<
|
||||
LayoutA,
|
||||
LayoutB,
|
||||
LayoutC,
|
||||
LayoutE
|
||||
>;
|
||||
|
||||
typename EpilogueOutputOp::Params epilogue;
|
||||
|
||||
Arguments() {}
|
||||
|
||||
/// constructs an arguments structure
|
||||
Arguments(
|
||||
GemmUniversalMode mode,
|
||||
GemmCoord problem_size,
|
||||
int batch_count,
|
||||
typename EpilogueOutputOp::Params epilogue,
|
||||
void const * ptr_A,
|
||||
void const * ptr_B,
|
||||
void const * ptr_C,
|
||||
void * ptr_D,
|
||||
void const * ptr_E,
|
||||
int64_t batch_stride_A,
|
||||
int64_t batch_stride_B,
|
||||
int64_t batch_stride_C,
|
||||
int64_t batch_stride_D,
|
||||
int64_t batch_stride_E,
|
||||
typename LayoutA::Stride::LongIndex lda,
|
||||
typename LayoutB::Stride::LongIndex ldb,
|
||||
typename LayoutC::Stride::LongIndex ldc,
|
||||
typename LayoutC::Stride::LongIndex ldd,
|
||||
typename LayoutC::Stride::LongIndex lde)
|
||||
:
|
||||
Base(
|
||||
mode, problem_size, batch_count,
|
||||
ptr_A, ptr_B, ptr_C, ptr_D, ptr_E,
|
||||
batch_stride_A, batch_stride_B, batch_stride_C, batch_stride_D, batch_stride_E,
|
||||
lda, ldb, ldc, ldd, lde
|
||||
),
|
||||
epilogue(epilogue)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversal::Arguments::Arguments() - problem_size: " << problem_size);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Structure for precomputing values in host memory and passing to kernels
|
||||
//
|
||||
|
||||
/// Parameters structure
|
||||
struct Params : detail::SparseUniversalParamsBase<
|
||||
Mma,
|
||||
Epilogue,
|
||||
Arguments,
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>
|
||||
{
|
||||
using ParamsBase = detail::SparseUniversalParamsBase<
|
||||
Mma,
|
||||
Epilogue,
|
||||
Arguments,
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>;
|
||||
|
||||
typename EpilogueOutputOp::Params output_op;
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
Params() = default;
|
||||
|
||||
/// Constructor
|
||||
Params(
|
||||
Arguments const &args, /// GEMM application arguments
|
||||
int device_sms, /// Number of SMs on the device
|
||||
int sm_occupancy) /// Kernel SM occupancy (in thread blocks)
|
||||
:
|
||||
ParamsBase(args, device_sms, sm_occupancy),
|
||||
output_op(args.epilogue)
|
||||
{}
|
||||
|
||||
/// Lightweight update given a subset of arguments.
|
||||
void update(Arguments const &args)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversal::Params::update()");
|
||||
|
||||
// Update input/output pointers
|
||||
this->ptr_A = const_cast<void *>(args.ptr_A);
|
||||
this->ptr_B = const_cast<void *>(args.ptr_B);
|
||||
this->ptr_C = const_cast<void *>(args.ptr_C);
|
||||
this->ptr_D = args.ptr_D;
|
||||
this->ptr_E = const_cast<void *>(args.ptr_E);
|
||||
|
||||
this->batch_stride_A = args.batch_stride_A;
|
||||
this->batch_stride_B = args.batch_stride_B;
|
||||
this->batch_stride_C = args.batch_stride_C;
|
||||
this->batch_stride_D = args.batch_stride_D;
|
||||
this->batch_stride_E = args.batch_stride_E;
|
||||
|
||||
output_op = args.epilogue;
|
||||
}
|
||||
};
|
||||
|
||||
/// Shared memory storage structure
|
||||
union SharedStorage {
|
||||
typename Mma::SharedStorage main_loop;
|
||||
typename Epilogue::SharedStorage epilogue;
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Determines whether kernel satisfies alignment
|
||||
static Status can_implement(
|
||||
cutlass::gemm::GemmCoord const & problem_size,
|
||||
GemmUniversalMode mode,
|
||||
int split_k_count)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversal::can_implement()");
|
||||
|
||||
static int const kAlignmentA = (cute::is_same<LayoutA,
|
||||
layout::ColumnMajorInterleaved<32>>::value)
|
||||
? 32
|
||||
: (cute::is_same<LayoutA,
|
||||
layout::ColumnMajorInterleaved<64>>::value)
|
||||
? 64
|
||||
: Mma::IteratorA::AccessType::kElements;
|
||||
static int const kAlignmentB = (cute::is_same<LayoutB,
|
||||
layout::RowMajorInterleaved<32>>::value)
|
||||
? 32
|
||||
: (cute::is_same<LayoutB,
|
||||
layout::RowMajorInterleaved<64>>::value)
|
||||
? 64
|
||||
: Mma::IteratorB::AccessType::kElements;
|
||||
static int const kAlignmentC = (cute::is_same<LayoutC,
|
||||
layout::ColumnMajorInterleaved<32>>::value)
|
||||
? 32
|
||||
: (cute::is_same<LayoutC,
|
||||
layout::ColumnMajorInterleaved<64>>::value)
|
||||
? 64
|
||||
: Epilogue::OutputTileIterator::kElementsPerAccess;
|
||||
|
||||
static int const kAlignmentE = Mma::IteratorE::AccessType::kElements;
|
||||
|
||||
bool isAMisaligned = false;
|
||||
bool isBMisaligned = false;
|
||||
bool isCMisaligned = false;
|
||||
bool isEMisaligned = false;
|
||||
|
||||
if (cute::is_same<LayoutA, layout::RowMajor>::value) {
|
||||
isAMisaligned = (problem_size.k() / kSparse) % kAlignmentA;
|
||||
} else if (cute::is_same<LayoutA, layout::ColumnMajor>::value) {
|
||||
isAMisaligned = problem_size.m() % kAlignmentA;
|
||||
} else if (cute::is_same<LayoutA, layout::ColumnMajorInterleaved<32>>::value
|
||||
|| cute::is_same<LayoutA, layout::ColumnMajorInterleaved<64>>::value) {
|
||||
isAMisaligned = (problem_size.k() / kSparse) % kAlignmentA;
|
||||
}
|
||||
|
||||
if (cute::is_same<LayoutB, layout::RowMajor>::value) {
|
||||
isBMisaligned = problem_size.n() % kAlignmentB;
|
||||
} else if (cute::is_same<LayoutB, layout::ColumnMajor>::value) {
|
||||
isBMisaligned = (problem_size.k() / kSparse) % kAlignmentB;
|
||||
} else if (cute::is_same<LayoutB, layout::RowMajorInterleaved<32>>::value
|
||||
|| cute::is_same<LayoutB, layout::RowMajorInterleaved<64>>::value) {
|
||||
isBMisaligned = (problem_size.k() / kSparse) % kAlignmentB;
|
||||
}
|
||||
|
||||
if (cute::is_same<LayoutC, layout::RowMajor>::value) {
|
||||
isCMisaligned = problem_size.n() % kAlignmentC;
|
||||
} else if (cute::is_same<LayoutC, layout::ColumnMajor>::value) {
|
||||
isCMisaligned = problem_size.m() % kAlignmentC;
|
||||
} else if (cute::is_same<LayoutC, layout::ColumnMajorInterleaved<32>>::value
|
||||
|| cute::is_same<LayoutC, layout::ColumnMajorInterleaved<64>>::value) {
|
||||
isCMisaligned = problem_size.n() % kAlignmentC;
|
||||
}
|
||||
|
||||
isEMisaligned = (problem_size.m() % kAlignmentE)
|
||||
|| ((problem_size.k() / kSparse) % kAlignmentE);
|
||||
|
||||
// The k dimension has to be the multiple of the Threadblock k because out
|
||||
// of bound meta data would be initialized to 0 by acync.zfill but 0 is not
|
||||
// a valid meta data.
|
||||
if (problem_size.k() % Mma::Shape::kK) {
|
||||
isEMisaligned = true;
|
||||
}
|
||||
|
||||
if (mode == GemmUniversalMode::kGemm
|
||||
|| mode == GemmUniversalMode::kGemmSplitKParallel) {
|
||||
if ((problem_size.k() / split_k_count) % Mma::Shape::kK) {
|
||||
isEMisaligned = true;
|
||||
}
|
||||
}
|
||||
|
||||
// M dimension has to be multiple of 32 (sparse float) or 16 (sparse int)
|
||||
// because of the row reordering of operand E
|
||||
static int const kAlignmentM = (sizeof(ElementE) == 2) ? 32 : 16;
|
||||
|
||||
if (problem_size.m() % kAlignmentM) {
|
||||
isEMisaligned = true;
|
||||
}
|
||||
|
||||
if (isAMisaligned) {
|
||||
CUTLASS_TRACE_HOST(" returning kErrorMisalignedOperand for A operand");
|
||||
return Status::kErrorMisalignedOperand;
|
||||
}
|
||||
|
||||
if (isBMisaligned) {
|
||||
CUTLASS_TRACE_HOST(" returning kErrorMisalignedOperand for B operand");
|
||||
return Status::kErrorMisalignedOperand;
|
||||
}
|
||||
|
||||
if (isCMisaligned) {
|
||||
CUTLASS_TRACE_HOST(" returning kErrorMisalignedOperand for C operand");
|
||||
return Status::kErrorMisalignedOperand;
|
||||
}
|
||||
|
||||
if (isEMisaligned) {
|
||||
CUTLASS_TRACE_HOST(" returning kErrorMisalignedOperand for E operand");
|
||||
return Status::kErrorMisalignedOperand;
|
||||
}
|
||||
|
||||
CUTLASS_TRACE_HOST(" returning kSuccess");
|
||||
|
||||
return Status::kSuccess;
|
||||
}
|
||||
|
||||
static Status can_implement(Arguments const &args) {
|
||||
return can_implement(args.problem_size, args.mode, args.batch_count);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Device-only API
|
||||
//
|
||||
|
||||
// Factory invocation
|
||||
CUTLASS_DEVICE
|
||||
static void invoke(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
{
|
||||
GemmSparseUniversal op;
|
||||
op(params, shared_storage);
|
||||
}
|
||||
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void operator()(Params const ¶ms, SharedStorage &shared_storage) {
|
||||
ThreadblockSwizzle threadblock_swizzle;
|
||||
run_with_swizzle(params, shared_storage, threadblock_swizzle);
|
||||
}
|
||||
|
||||
/// Executes one GEMM with an externally-provided swizzling function
|
||||
CUTLASS_DEVICE
|
||||
void run_with_swizzle(Params const ¶ms, SharedStorage &shared_storage, ThreadblockSwizzle& threadblock_swizzle) {
|
||||
|
||||
cutlass::gemm::GemmCoord threadblock_tile_offset =
|
||||
threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
// Early exit if CTA is out of range
|
||||
if (params.grid_tiled_shape.m() <= threadblock_tile_offset.m() ||
|
||||
params.grid_tiled_shape.n() <= threadblock_tile_offset.n()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int offset_k = 0;
|
||||
int problem_size_k = params.problem_size.k();
|
||||
|
||||
ElementA *ptr_A = static_cast<ElementA *>(params.ptr_A);
|
||||
ElementB *ptr_B = static_cast<ElementB *>(params.ptr_B);
|
||||
ElementE *ptr_E = static_cast<ElementE *>(params.ptr_E);
|
||||
|
||||
//
|
||||
// Fetch pointers based on mode.
|
||||
//
|
||||
if (params.mode == GemmUniversalMode::kGemm ||
|
||||
params.mode == GemmUniversalMode::kGemmSplitKParallel) {
|
||||
|
||||
if (threadblock_tile_offset.k() + 1 < params.grid_tiled_shape.k()) {
|
||||
|
||||
problem_size_k = (threadblock_tile_offset.k() + 1) * params.gemm_k_size;
|
||||
}
|
||||
|
||||
offset_k = threadblock_tile_offset.k() * params.gemm_k_size;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kBatched) {
|
||||
ptr_A += threadblock_tile_offset.k() * params.batch_stride_A / kSparse;
|
||||
ptr_B += threadblock_tile_offset.k() * params.batch_stride_B;
|
||||
ptr_E += threadblock_tile_offset.k() * params.batch_stride_E / kSparse;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kArray) {
|
||||
ptr_A = static_cast<ElementA * const *>(params.ptr_A)[threadblock_tile_offset.k()];
|
||||
ptr_B = static_cast<ElementB * const *>(params.ptr_B)[threadblock_tile_offset.k()];
|
||||
ptr_E = static_cast<ElementE * const *>(params.ptr_E)[threadblock_tile_offset.k()];
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Compute initial location in logical coordinates
|
||||
cutlass::MatrixCoord tb_offset_A{
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
offset_k / kSparse,
|
||||
};
|
||||
|
||||
cutlass::MatrixCoord tb_offset_B{
|
||||
offset_k,
|
||||
threadblock_tile_offset.n() * Mma::Shape::kN
|
||||
};
|
||||
|
||||
cutlass::MatrixCoord tb_offset_E{
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
offset_k / kSparse / kElementsPerElementE,
|
||||
};
|
||||
|
||||
// Compute position within threadblock
|
||||
int thread_idx = threadIdx.x;
|
||||
|
||||
// Construct iterators to A and B operands
|
||||
typename Mma::IteratorA iterator_A(
|
||||
params.params_A,
|
||||
ptr_A,
|
||||
{params.problem_size.m(), problem_size_k / kSparse},
|
||||
thread_idx,
|
||||
tb_offset_A);
|
||||
|
||||
typename Mma::IteratorB iterator_B(
|
||||
params.params_B,
|
||||
ptr_B,
|
||||
{problem_size_k, params.problem_size.n()},
|
||||
thread_idx,
|
||||
tb_offset_B);
|
||||
|
||||
typename Mma::IteratorE iterator_E(
|
||||
params.params_E,
|
||||
ptr_E,
|
||||
{params.problem_size.m(), problem_size_k / kSparse / kElementsPerElementE},
|
||||
thread_idx,
|
||||
tb_offset_E);
|
||||
|
||||
// Broadcast the warp_id computed by lane 0 to ensure dependent code
|
||||
// is compiled as warp-uniform.
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
|
||||
int lane_idx = threadIdx.x % 32;
|
||||
|
||||
//
|
||||
// Main loop
|
||||
//
|
||||
|
||||
// Construct thread-scoped matrix multiply
|
||||
Mma mma(shared_storage.main_loop, thread_idx, warp_idx, lane_idx);
|
||||
|
||||
typename Mma::FragmentC accumulators;
|
||||
|
||||
accumulators.clear();
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
int gemm_k_iterations = (problem_size_k - offset_k + Mma::Shape::kK - 1) / Mma::Shape::kK;
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
mma(
|
||||
gemm_k_iterations,
|
||||
accumulators,
|
||||
iterator_A,
|
||||
iterator_B,
|
||||
iterator_E,
|
||||
accumulators);
|
||||
|
||||
//
|
||||
// Epilogue
|
||||
//
|
||||
|
||||
EpilogueOutputOp output_op(params.output_op);
|
||||
|
||||
//
|
||||
// Masked tile iterators constructed from members
|
||||
//
|
||||
|
||||
threadblock_tile_offset = threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
//assume identity swizzle
|
||||
MatrixCoord threadblock_offset(
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
threadblock_tile_offset.n() * Mma::Shape::kN
|
||||
);
|
||||
|
||||
int block_idx = threadblock_tile_offset.m() + threadblock_tile_offset.n() * params.grid_tiled_shape.m();
|
||||
|
||||
ElementC *ptr_C = static_cast<ElementC *>(params.ptr_C);
|
||||
ElementC *ptr_D = static_cast<ElementC *>(params.ptr_D);
|
||||
|
||||
//
|
||||
// Fetch pointers based on mode.
|
||||
//
|
||||
|
||||
// Construct the semaphore.
|
||||
Semaphore semaphore(params.semaphore + block_idx, thread_idx);
|
||||
|
||||
if (params.mode == GemmUniversalMode::kGemm) {
|
||||
|
||||
// If performing a reduction via split-K, fetch the initial synchronization
|
||||
if (params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
// Fetch the synchronization lock initially but do not block.
|
||||
semaphore.fetch();
|
||||
|
||||
// Indicate which position in a serial reduction the output operator is currently updating
|
||||
output_op.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
|
||||
}
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kGemmSplitKParallel) {
|
||||
ptr_D += threadblock_tile_offset.k() * params.batch_stride_D;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kBatched) {
|
||||
ptr_C += threadblock_tile_offset.k() * params.batch_stride_C;
|
||||
ptr_D += threadblock_tile_offset.k() * params.batch_stride_D;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kArray) {
|
||||
ptr_C = static_cast<ElementC * const *>(params.ptr_C)[threadblock_tile_offset.k()];
|
||||
ptr_D = static_cast<ElementC * const *>(params.ptr_D)[threadblock_tile_offset.k()];
|
||||
}
|
||||
|
||||
// Tile iterator loading from source tensor.
|
||||
typename Epilogue::OutputTileIterator iterator_C(
|
||||
params.params_C,
|
||||
ptr_C,
|
||||
params.problem_size.mn(),
|
||||
thread_idx,
|
||||
threadblock_offset
|
||||
);
|
||||
|
||||
// Tile iterator writing to destination tensor.
|
||||
typename Epilogue::OutputTileIterator iterator_D(
|
||||
params.params_D,
|
||||
ptr_D,
|
||||
params.problem_size.mn(),
|
||||
thread_idx,
|
||||
threadblock_offset
|
||||
);
|
||||
|
||||
Epilogue epilogue(
|
||||
shared_storage.epilogue,
|
||||
thread_idx,
|
||||
warp_idx,
|
||||
lane_idx);
|
||||
|
||||
// Wait on the semaphore - this latency may have been covered by iterator construction
|
||||
if (params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
// For subsequent threadblocks, the source matrix is held in the 'D' tensor.
|
||||
if (threadblock_tile_offset.k()) {
|
||||
iterator_C = iterator_D;
|
||||
}
|
||||
|
||||
semaphore.wait(threadblock_tile_offset.k());
|
||||
}
|
||||
|
||||
|
||||
// Execute the epilogue operator to update the destination tensor.
|
||||
epilogue(
|
||||
output_op,
|
||||
iterator_D,
|
||||
accumulators,
|
||||
iterator_C);
|
||||
|
||||
//
|
||||
// Release the semaphore
|
||||
//
|
||||
|
||||
if (params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
int lock = 0;
|
||||
if (params.grid_tiled_shape.k() == threadblock_tile_offset.k() + 1) {
|
||||
|
||||
// The final threadblock resets the semaphore for subsequent grids.
|
||||
lock = 0;
|
||||
}
|
||||
else {
|
||||
// Otherwise, the semaphore is incremented
|
||||
lock = threadblock_tile_offset.k() + 1;
|
||||
}
|
||||
|
||||
semaphore.release(lock);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -0,0 +1,609 @@
|
||||
/***************************************************************************************************
|
||||
* 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
|
||||
#include "cutlass/arch/arch.h"
|
||||
#include "cutlass/fast_math.h"
|
||||
#include "cutlass/matrix_coord.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/semaphore.h"
|
||||
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/kernel/params_universal_base.h"
|
||||
#include "cutlass/gemm/kernel/gemm_sparse_universal.h"
|
||||
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
typename Mma_, ///! Threadblock-scoped matrix multiply-accumulate
|
||||
typename Epilogue_, ///! Epilogue
|
||||
typename ThreadblockSwizzle_ ///! Threadblock swizzling function
|
||||
>
|
||||
class GemmSparseUniversalWithAbsmax {
|
||||
public:
|
||||
using Base = GemmSparseUniversal<Mma_, Epilogue_, ThreadblockSwizzle_>;
|
||||
|
||||
using Mma = Mma_;
|
||||
using Epilogue = Epilogue_;
|
||||
using EpilogueOutputOp = typename Epilogue::OutputOp;
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
|
||||
static int const kSparse = Mma::kSparse;
|
||||
static int const kMetaSizeInBits = Mma::kMetaSizeInBits;
|
||||
static int const kMaxID2 = Mma::kMaxID2;
|
||||
static int const kElementsPerElementE = Mma::kElementsPerElementE;
|
||||
|
||||
using ElementE = typename Mma::ElementE;
|
||||
using LayoutE = typename Mma::LayoutE;
|
||||
|
||||
using ElementA = typename Mma::IteratorA::Element;
|
||||
using LayoutA = typename Mma::IteratorA::Layout;
|
||||
using ElementB = typename Mma::IteratorB::Element;
|
||||
using LayoutB = typename Mma::IteratorB::Layout;
|
||||
using ElementC = typename Epilogue::OutputTileIterator::Element;
|
||||
using LayoutC = typename Epilogue::OutputTileIterator::Layout;
|
||||
using ElementAux = typename Epilogue::AuxOutputTileIterator::Element;
|
||||
using LayoutAux = typename Epilogue::AuxOutputTileIterator::Layout;
|
||||
using ElementVector = typename Epilogue::ElementVector;
|
||||
|
||||
static ComplexTransform const kTransformA = Mma::kTransformA;
|
||||
static ComplexTransform const kTransformB = Mma::kTransformB;
|
||||
using Operator = typename Mma::Operator;
|
||||
|
||||
using OperatorClass = typename Mma::Operator::OperatorClass;
|
||||
using ThreadblockShape = typename Mma::Shape;
|
||||
using WarpShape = typename Mma::Operator::Shape;
|
||||
using InstructionShape = typename Mma::Policy::Operator::InstructionShape;
|
||||
using ArchTag = typename Mma::ArchTag;
|
||||
|
||||
static int const kStages = Mma::kStages;
|
||||
static int const kAlignmentA = Mma::IteratorA::AccessType::kElements;
|
||||
static int const kAlignmentB = Mma::IteratorB::AccessType::kElements;
|
||||
static int const kAlignmentC = Epilogue::OutputTileIterator::kElementsPerAccess;
|
||||
|
||||
/// Warp count (concept: GemmShape)
|
||||
using WarpCount = typename Mma::WarpCount;
|
||||
static int const kThreadCount = 32 * WarpCount::kCount;
|
||||
|
||||
/// Split-K preserves splits that are 128b aligned
|
||||
static int const kSplitKAlignment = const_max(128 / sizeof_bits<ElementA>::value, 128 / sizeof_bits<ElementB>::value);
|
||||
|
||||
//
|
||||
// Structures
|
||||
//
|
||||
|
||||
/// Argument structure
|
||||
struct Arguments : detail::SparseUniversalArgumentsBase<
|
||||
LayoutA,
|
||||
LayoutB,
|
||||
LayoutC,
|
||||
LayoutE
|
||||
> {
|
||||
using Base = detail::SparseUniversalArgumentsBase<
|
||||
LayoutA,
|
||||
LayoutB,
|
||||
LayoutC,
|
||||
LayoutE
|
||||
>;
|
||||
|
||||
void const* ptr_Aux;
|
||||
void const* ptr_Vector;
|
||||
int64_t batch_stride_Aux;
|
||||
int64_t batch_stride_Vector;
|
||||
typename LayoutAux::Stride::LongIndex ldaux;
|
||||
int64_t ldvector;
|
||||
|
||||
typename EpilogueOutputOp::Params epilogue;
|
||||
|
||||
Arguments() {}
|
||||
|
||||
/// constructs an arguments structure
|
||||
Arguments(
|
||||
GemmUniversalMode mode,
|
||||
GemmCoord problem_size,
|
||||
int batch_count,
|
||||
typename EpilogueOutputOp::Params epilogue,
|
||||
void const * ptr_A,
|
||||
void const * ptr_B,
|
||||
void const * ptr_C,
|
||||
void * ptr_D,
|
||||
void const * ptr_E,
|
||||
void const * ptr_Aux,
|
||||
void const * ptr_Vector,
|
||||
int64_t batch_stride_A,
|
||||
int64_t batch_stride_B,
|
||||
int64_t batch_stride_C,
|
||||
int64_t batch_stride_D,
|
||||
int64_t batch_stride_E,
|
||||
int64_t batch_stride_Aux,
|
||||
int64_t batch_stride_Vector,
|
||||
typename LayoutA::Stride::LongIndex lda,
|
||||
typename LayoutB::Stride::LongIndex ldb,
|
||||
typename LayoutC::Stride::LongIndex ldc,
|
||||
typename LayoutC::Stride::LongIndex ldd,
|
||||
typename LayoutC::Stride::LongIndex lde,
|
||||
typename LayoutAux::Stride::LongIndex ldaux,
|
||||
int64_t ldvector
|
||||
)
|
||||
:
|
||||
Base(
|
||||
mode, problem_size, batch_count,
|
||||
ptr_A, ptr_B, ptr_C, ptr_D, ptr_E,
|
||||
batch_stride_A, batch_stride_B, batch_stride_C, batch_stride_D, batch_stride_E,
|
||||
lda, ldb, ldc, ldd, lde
|
||||
),
|
||||
ptr_Aux(ptr_Aux),
|
||||
ptr_Vector(ptr_Vector),
|
||||
batch_stride_Aux(batch_stride_Aux),
|
||||
batch_stride_Vector(batch_stride_Vector),
|
||||
ldaux(ldaux),
|
||||
ldvector(ldvector),
|
||||
epilogue(epilogue)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Structure for precomputing values in host memory and passing to kernels
|
||||
//
|
||||
|
||||
/// Parameters structure
|
||||
struct Params : detail::SparseUniversalParamsBase<
|
||||
Mma,
|
||||
Epilogue,
|
||||
Arguments,
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>
|
||||
{
|
||||
using ParamsBase = detail::SparseUniversalParamsBase<
|
||||
Mma,
|
||||
Epilogue,
|
||||
Arguments,
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>;
|
||||
|
||||
typename Epilogue::AuxOutputTileIterator::Params params_Aux;
|
||||
int64_t ldvector;
|
||||
|
||||
void* ptr_Aux;
|
||||
void* ptr_Vector;
|
||||
|
||||
int64_t batch_stride_Aux;
|
||||
int64_t batch_stride_Vector;
|
||||
typename EpilogueOutputOp::Params output_op;
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
Params() = default;
|
||||
|
||||
/// Constructor
|
||||
Params(
|
||||
Arguments const &args, /// GEMM application arguments
|
||||
int device_sms, /// Number of SMs on the device
|
||||
int sm_occupancy) /// Kernel SM occupancy (in thread blocks)
|
||||
:
|
||||
ParamsBase(args, device_sms, sm_occupancy),
|
||||
params_Aux(args.ldaux),
|
||||
ldvector(args.ldvector),
|
||||
ptr_Aux(const_cast<void *>(args.ptr_Aux)),
|
||||
ptr_Vector(const_cast<void *>(args.ptr_Vector)),
|
||||
batch_stride_Aux(args.batch_stride_Aux),
|
||||
batch_stride_Vector(args.batch_stride_Vector),
|
||||
output_op(args.epilogue)
|
||||
{}
|
||||
|
||||
/// Lightweight update given a subset of arguments.
|
||||
void update(Arguments const &args)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversal::Params::update()");
|
||||
|
||||
// Update input/output pointers
|
||||
this->ptr_A = const_cast<void *>(args.ptr_A);
|
||||
this->ptr_B = const_cast<void *>(args.ptr_B);
|
||||
this->ptr_C = const_cast<void *>(args.ptr_C);
|
||||
this->ptr_D = args.ptr_D;
|
||||
this->ptr_E = const_cast<void *>(args.ptr_E);
|
||||
ptr_Aux = const_cast<void *>(args.ptr_Aux);
|
||||
ptr_Vector = const_cast<void *>(args.ptr_Vector);
|
||||
|
||||
this->batch_stride_A = args.batch_stride_A;
|
||||
this->batch_stride_B = args.batch_stride_B;
|
||||
this->batch_stride_C = args.batch_stride_C;
|
||||
this->batch_stride_D = args.batch_stride_D;
|
||||
this->batch_stride_E = args.batch_stride_E;
|
||||
this->batch_stride_Aux = args.batch_stride_Aux;
|
||||
batch_stride_Vector = args.batch_stride_Vector;
|
||||
|
||||
output_op = args.epilogue;
|
||||
}
|
||||
};
|
||||
|
||||
/// Shared memory storage structure
|
||||
union SharedStorage {
|
||||
typename Mma::SharedStorage main_loop;
|
||||
typename Epilogue::SharedStorage epilogue;
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Determines whether kernel satisfies alignment
|
||||
static Status can_implement(
|
||||
cutlass::gemm::GemmCoord const & problem_size,
|
||||
GemmUniversalMode mode,
|
||||
int split_k_count) {
|
||||
return Base::can_implement(problem_size, mode, split_k_count);
|
||||
}
|
||||
|
||||
static Status can_implement(Arguments const &args) {
|
||||
return can_implement(args.problem_size, args.mode, args.batch_count);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Device-only API
|
||||
//
|
||||
|
||||
// Factory invocation
|
||||
CUTLASS_DEVICE
|
||||
static void invoke(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
{
|
||||
GemmSparseUniversalWithAbsmax op;
|
||||
op(params, shared_storage);
|
||||
}
|
||||
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void operator()(Params const ¶ms, SharedStorage &shared_storage) {
|
||||
ThreadblockSwizzle threadblock_swizzle;
|
||||
run_with_swizzle(params, shared_storage, threadblock_swizzle);
|
||||
}
|
||||
|
||||
/// Executes one GEMM with an externally-provided swizzling function
|
||||
CUTLASS_DEVICE
|
||||
void run_with_swizzle(Params const ¶ms, SharedStorage &shared_storage, ThreadblockSwizzle& threadblock_swizzle) {
|
||||
|
||||
cutlass::gemm::GemmCoord threadblock_tile_offset =
|
||||
threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
// Early exit if CTA is out of range
|
||||
if (params.grid_tiled_shape.m() <= threadblock_tile_offset.m() ||
|
||||
params.grid_tiled_shape.n() <= threadblock_tile_offset.n()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int offset_k = 0;
|
||||
int problem_size_k = params.problem_size.k();
|
||||
|
||||
ElementA *ptr_A = static_cast<ElementA *>(params.ptr_A);
|
||||
ElementB *ptr_B = static_cast<ElementB *>(params.ptr_B);
|
||||
ElementE *ptr_E = static_cast<ElementE *>(params.ptr_E);
|
||||
|
||||
//
|
||||
// Fetch pointers based on mode.
|
||||
//
|
||||
if (params.mode == GemmUniversalMode::kGemm ||
|
||||
params.mode == GemmUniversalMode::kGemmSplitKParallel) {
|
||||
|
||||
if (threadblock_tile_offset.k() + 1 < params.grid_tiled_shape.k()) {
|
||||
|
||||
problem_size_k = (threadblock_tile_offset.k() + 1) * params.gemm_k_size;
|
||||
}
|
||||
|
||||
offset_k = threadblock_tile_offset.k() * params.gemm_k_size;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kBatched) {
|
||||
ptr_A += threadblock_tile_offset.k() * params.batch_stride_A / kSparse;
|
||||
ptr_B += threadblock_tile_offset.k() * params.batch_stride_B;
|
||||
ptr_E += threadblock_tile_offset.k() * params.batch_stride_E / kSparse;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kArray) {
|
||||
ptr_A = static_cast<ElementA * const *>(params.ptr_A)[threadblock_tile_offset.k()];
|
||||
ptr_B = static_cast<ElementB * const *>(params.ptr_B)[threadblock_tile_offset.k()];
|
||||
ptr_E = static_cast<ElementE * const *>(params.ptr_E)[threadblock_tile_offset.k()];
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Compute initial location in logical coordinates
|
||||
cutlass::MatrixCoord tb_offset_A{
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
offset_k / kSparse,
|
||||
};
|
||||
|
||||
cutlass::MatrixCoord tb_offset_B{
|
||||
offset_k,
|
||||
threadblock_tile_offset.n() * Mma::Shape::kN
|
||||
};
|
||||
|
||||
cutlass::MatrixCoord tb_offset_E{
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
offset_k / kSparse / kElementsPerElementE,
|
||||
};
|
||||
|
||||
// Compute position within threadblock
|
||||
int thread_idx = threadIdx.x;
|
||||
|
||||
// Construct iterators to A and B operands
|
||||
typename Mma::IteratorA iterator_A(
|
||||
params.params_A,
|
||||
ptr_A,
|
||||
{params.problem_size.m(), problem_size_k / kSparse},
|
||||
thread_idx,
|
||||
tb_offset_A);
|
||||
|
||||
typename Mma::IteratorB iterator_B(
|
||||
params.params_B,
|
||||
ptr_B,
|
||||
{problem_size_k, params.problem_size.n()},
|
||||
thread_idx,
|
||||
tb_offset_B);
|
||||
|
||||
typename Mma::IteratorE iterator_E(
|
||||
params.params_E,
|
||||
ptr_E,
|
||||
{params.problem_size.m(), problem_size_k / kSparse / kElementsPerElementE},
|
||||
thread_idx,
|
||||
tb_offset_E);
|
||||
|
||||
// Broadcast the warp_id computed by lane 0 to ensure dependent code
|
||||
// is compiled as warp-uniform.
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
|
||||
int lane_idx = threadIdx.x % 32;
|
||||
|
||||
//
|
||||
// Main loop
|
||||
//
|
||||
|
||||
// Construct thread-scoped matrix multiply
|
||||
Mma mma(shared_storage.main_loop, thread_idx, warp_idx, lane_idx);
|
||||
|
||||
typename Mma::FragmentC accumulators;
|
||||
|
||||
accumulators.clear();
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
int gemm_k_iterations = (problem_size_k - offset_k + Mma::Shape::kK - 1) / Mma::Shape::kK;
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
mma(
|
||||
gemm_k_iterations,
|
||||
accumulators,
|
||||
iterator_A,
|
||||
iterator_B,
|
||||
iterator_E,
|
||||
accumulators);
|
||||
|
||||
//
|
||||
// Epilogue
|
||||
//
|
||||
|
||||
EpilogueOutputOp output_op(params.output_op);
|
||||
|
||||
//
|
||||
// Masked tile iterators constructed from members
|
||||
//
|
||||
|
||||
threadblock_tile_offset = threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
//assume identity swizzle
|
||||
MatrixCoord threadblock_offset(
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
threadblock_tile_offset.n() * Mma::Shape::kN
|
||||
);
|
||||
|
||||
int block_idx = threadblock_tile_offset.m() + threadblock_tile_offset.n() * params.grid_tiled_shape.m();
|
||||
|
||||
ElementC *ptr_C = static_cast<ElementC *>(params.ptr_C);
|
||||
ElementC *ptr_D = static_cast<ElementC *>(params.ptr_D);
|
||||
ElementAux * ptr_Aux = static_cast<ElementAux *>(params.ptr_Aux);
|
||||
ElementVector * ptr_Vector = static_cast<ElementVector *>(params.ptr_Vector);
|
||||
|
||||
//
|
||||
// Fetch pointers based on mode.
|
||||
//
|
||||
|
||||
// Construct the semaphore.
|
||||
Semaphore semaphore(params.semaphore + block_idx, thread_idx);
|
||||
|
||||
if (params.mode == GemmUniversalMode::kGemm) {
|
||||
|
||||
// If performing a reduction via split-K, fetch the initial synchronization
|
||||
if (params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
// Fetch the synchronization lock initially but do not block.
|
||||
semaphore.fetch();
|
||||
|
||||
// Indicate which position in a serial reduction the output operator is currently updating
|
||||
output_op.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
|
||||
}
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kGemmSplitKParallel) {
|
||||
ptr_D += threadblock_tile_offset.k() * params.batch_stride_D;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kBatched) {
|
||||
ptr_C += threadblock_tile_offset.k() * params.batch_stride_C;
|
||||
ptr_D += threadblock_tile_offset.k() * params.batch_stride_D;
|
||||
if (ptr_Aux) {
|
||||
ptr_Aux += threadblock_tile_offset.k() * params.batch_stride_Aux;
|
||||
}
|
||||
if (ptr_Vector) {
|
||||
ptr_Vector += threadblock_tile_offset.k() * params.batch_stride_Vector;
|
||||
}
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kArray) {
|
||||
ptr_C = static_cast<ElementC * const *>(params.ptr_C)[threadblock_tile_offset.k()];
|
||||
ptr_D = static_cast<ElementC * const *>(params.ptr_D)[threadblock_tile_offset.k()];
|
||||
if (ptr_Aux) {
|
||||
ptr_Aux = static_cast<ElementAux * const *>(params.ptr_Aux)[threadblock_tile_offset.k()];
|
||||
}
|
||||
if (ptr_Vector) {
|
||||
ptr_Vector = static_cast<ElementVector * const *>(params.ptr_Vector)[threadblock_tile_offset.k()];
|
||||
}
|
||||
}
|
||||
|
||||
// Move to appropriate location for this output tile
|
||||
if (ptr_Vector) {
|
||||
ptr_Vector += threadblock_offset.column() + threadblock_tile_offset.m() * params.ldvector;
|
||||
}
|
||||
|
||||
// Tile iterator loading from source tensor.
|
||||
typename Epilogue::OutputTileIterator iterator_C(
|
||||
params.params_C,
|
||||
ptr_C,
|
||||
params.problem_size.mn(),
|
||||
thread_idx,
|
||||
threadblock_offset
|
||||
);
|
||||
|
||||
// Tile iterator writing to destination tensor.
|
||||
typename Epilogue::OutputTileIterator iterator_D(
|
||||
params.params_D,
|
||||
ptr_D,
|
||||
params.problem_size.mn(),
|
||||
thread_idx,
|
||||
threadblock_offset
|
||||
);
|
||||
|
||||
// Tile iterator writing to auxiliary destination tensor.
|
||||
typename Epilogue::AuxOutputTileIterator iterator_Aux(
|
||||
params.params_Aux,
|
||||
// Only the final block writes the auxiliary tensor
|
||||
((params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) &&
|
||||
(params.grid_tiled_shape.k() != threadblock_tile_offset.k() + 1))
|
||||
? nullptr
|
||||
: ptr_Aux,
|
||||
params.problem_size.mn(),
|
||||
thread_idx,
|
||||
threadblock_offset
|
||||
);
|
||||
|
||||
Epilogue epilogue(
|
||||
shared_storage.epilogue,
|
||||
thread_idx,
|
||||
warp_idx,
|
||||
lane_idx);
|
||||
|
||||
// Wait on the semaphore - this latency may have been covered by iterator construction
|
||||
if (params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
// For subsequent threadblocks, the source matrix is held in the 'D' tensor.
|
||||
if (threadblock_tile_offset.k()) {
|
||||
iterator_C = iterator_D;
|
||||
}
|
||||
|
||||
semaphore.wait(threadblock_tile_offset.k());
|
||||
}
|
||||
|
||||
|
||||
// Execute the epilogue operator to update the destination tensor.
|
||||
epilogue(
|
||||
output_op,
|
||||
// Only the final block uses Vector
|
||||
((params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) &&
|
||||
(params.grid_tiled_shape.k() != threadblock_tile_offset.k() + 1))
|
||||
? nullptr
|
||||
: ptr_Vector,
|
||||
iterator_D,
|
||||
accumulators,
|
||||
iterator_C,
|
||||
iterator_Aux,
|
||||
params.problem_size.mn(),
|
||||
threadblock_offset);
|
||||
|
||||
//
|
||||
// Release the semaphore
|
||||
//
|
||||
|
||||
if (params.mode == GemmUniversalMode::kGemm && params.grid_tiled_shape.k() > 1) {
|
||||
|
||||
int lock = 0;
|
||||
if (params.grid_tiled_shape.k() == threadblock_tile_offset.k() + 1) {
|
||||
|
||||
// The final threadblock resets the semaphore for subsequent grids.
|
||||
lock = 0;
|
||||
}
|
||||
else {
|
||||
// Otherwise, the semaphore is incremented
|
||||
lock = threadblock_tile_offset.k() + 1;
|
||||
}
|
||||
|
||||
semaphore.release(lock);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -30,40 +30,13 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler.hpp"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::kernel {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Stateless universal device GEMM kernel type that treats GEMM as
|
||||
* a composition of a collective mainloop and a collective epilogue.
|
||||
*
|
||||
* Supports both the 2.x and 3.x APIs based on whether the first type is
|
||||
* a cute::tuple<> or not.
|
||||
* 2.x API implementation: cutlass/gemm/kernel/gemm_universal.h
|
||||
* 3.x API implementation: cutlass/gemm/kernel/gemm_*.hpp
|
||||
*
|
||||
* In the following declaration, the name preceding the 'Or' refers to
|
||||
* 3.x API type argument order, and the name succeeding the 'Or' refers to
|
||||
* 2.x API type argument order. Template arguments without two names
|
||||
* belong to the 3.x API only.
|
||||
**/
|
||||
template <
|
||||
class ProblemShapeOrThreadblockMma_, // (m, n, k) or (m, n, k, l)
|
||||
class CollectiveMainloopOrEpilogue_,
|
||||
class CollectiveEpilogueOrThreadblockSwizzle_,
|
||||
class TileScheduler_ = void,
|
||||
class Enable = void
|
||||
>
|
||||
class GemmUniversal;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// In cases where ProblemShape is not a tuple, this is used to check if the
|
||||
// underlying problem shape type is aliased within or not.
|
||||
// Used for dispatching GemmUniversal to 2.x API or 3.x API
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 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
|
||||
|
||||
namespace cutlass::gemm::kernel {
|
||||
|
||||
|
||||
/*
|
||||
* Stateless universal device GEMM kernel type that treats GEMM as
|
||||
* a composition of a collective mainloop and a collective epilogue.
|
||||
*
|
||||
* Supports both the 2.x and 3.x APIs based on whether the first type is
|
||||
* a cute::tuple<> or not.
|
||||
* 2.x API implementation: cutlass/gemm/kernel/gemm_universal.h
|
||||
* 3.x API implementation: cutlass/gemm/kernel/gemm_*.hpp
|
||||
*
|
||||
* In the following declaration, the name preceding the 'Or' refers to
|
||||
* 3.x API type argument order, and the name succeeding the 'Or' refers to
|
||||
* 2.x API type argument order. Template arguments without two names
|
||||
* belong to the 3.x API only.
|
||||
**/
|
||||
template <
|
||||
class ProblemShapeOrThreadblockMma_, // (m, n, k) or (m, n, k, l)
|
||||
class CollectiveMainloopOrEpilogue_,
|
||||
class CollectiveEpilogueOrThreadblockSwizzle_,
|
||||
class TileScheduler_ = void,
|
||||
class Enable = void
|
||||
>
|
||||
class GemmUniversal;
|
||||
|
||||
|
||||
} // namespace cutlass::gemm::kernel
|
||||
|
||||
@@ -196,10 +196,7 @@ static_assert(is_valid_tile_scheduler, "SM70 kernel does not support specializin
|
||||
// Separate out problem shape for convenience
|
||||
// Optionally append 1s until problem shape is rank-4 in case its is only rank-3 (MNK)
|
||||
auto problem_shape_MNKL = append<4>(params.problem_shape, Int<1>{});
|
||||
auto M = get<0>(problem_shape_MNKL);
|
||||
auto N = get<1>(problem_shape_MNKL);
|
||||
auto K = get<2>(problem_shape_MNKL);
|
||||
auto L = get<3>(problem_shape_MNKL);
|
||||
auto [M,N,K,L] = problem_shape_MNKL;
|
||||
|
||||
// Preconditions
|
||||
static_assert(cute::rank(StrideA{}) == 3, "StrideA must be rank-3: [M, K, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
|
||||
@@ -40,8 +40,9 @@
|
||||
#include "cutlass/epilogue/collective/detail.hpp"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/group_array_problem_shape.hpp"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/group_array_problem_shape.hpp"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cute/tensor.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
@@ -79,9 +80,9 @@ public:
|
||||
using ArchTag = typename CollectiveMainloop::ArchTag;
|
||||
using ElementA = typename CollectiveMainloop::ElementA;
|
||||
using StrideA = typename CollectiveMainloop::StrideA;
|
||||
using UnderlyingStrideA = typename CollectiveMainloop::UnderlyingStrideA;
|
||||
using InternalStrideA = typename CollectiveMainloop::InternalStrideA;
|
||||
using ElementB = typename CollectiveMainloop::ElementB;
|
||||
using UnderlyingStrideB = typename CollectiveMainloop::UnderlyingStrideB;
|
||||
using InternalStrideB = typename CollectiveMainloop::InternalStrideB;
|
||||
using StrideB = typename CollectiveMainloop::StrideB;
|
||||
using DispatchPolicy = typename CollectiveMainloop::DispatchPolicy;
|
||||
using Schedule = typename DispatchPolicy::Schedule;
|
||||
@@ -94,18 +95,18 @@ public:
|
||||
using CollectiveEpilogue = CollectiveEpilogue_;
|
||||
using ElementC = typename CollectiveEpilogue::ElementC;
|
||||
using StrideC = typename CollectiveEpilogue::StrideC;
|
||||
using UnderlyingStrideC = typename CollectiveEpilogue::UnderlyingStrideC;
|
||||
using InternalStrideC = typename CollectiveEpilogue::InternalStrideC;
|
||||
using ElementD = typename CollectiveEpilogue::ElementD;
|
||||
using StrideD = typename CollectiveEpilogue::StrideD;
|
||||
using UnderlyingStrideD = typename CollectiveEpilogue::UnderlyingStrideD;
|
||||
using InternalStrideD = typename CollectiveEpilogue::InternalStrideD;
|
||||
using EpilogueArguments = typename CollectiveEpilogue::Arguments;
|
||||
using EpilogueParams = typename CollectiveEpilogue::Params;
|
||||
|
||||
static_assert(ArchTag::kMinComputeCapability >= 90);
|
||||
static_assert(cute::is_void_v<TileScheduler_>,
|
||||
"Ptr-Array Cooperative and Grouped Gemm Cooperative kernel only supports the default scheduler.");
|
||||
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<UnderlyingStrideA, StrideA>;
|
||||
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<InternalStrideA, StrideA>;
|
||||
|
||||
using TileScheduler = cute::conditional_t<IsGroupedGemmKernel,
|
||||
typename detail::TileSchedulerSelector<
|
||||
@@ -150,7 +151,10 @@ public:
|
||||
|
||||
struct TensorMapStorage : cute::aligned_struct<128> {
|
||||
using MainloopTensorMapStorage = typename CollectiveMainloop::TensorMapStorage;
|
||||
using EpilogueTensorMapStorage = typename CollectiveEpilogue::TensorMapStorage;
|
||||
|
||||
alignas(128) MainloopTensorMapStorage mainloop;
|
||||
alignas(128) EpilogueTensorMapStorage epilogue;
|
||||
} tensormaps;
|
||||
};
|
||||
|
||||
@@ -211,7 +215,7 @@ public:
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
|
||||
void* epilogue_workspace = workspace_ptr + workspace_offset;
|
||||
workspace_offset += CollectiveEpilogue::get_workspace_size(problem_shapes, args.epilogue);
|
||||
workspace_offset += CollectiveEpilogue::get_workspace_size(problem_shapes, args.epilogue, args.hw_info.sm_count);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
|
||||
void* mainloop_workspace = workspace_ptr + workspace_offset;
|
||||
@@ -230,7 +234,7 @@ public:
|
||||
else {
|
||||
scheduler = TileScheduler::to_underlying_arguments(
|
||||
problem_shapes.get_host_problem_shape(), TileShape{}, ClusterShape{}, hw_info, args.scheduler, scheduler_workspace, NumEpilogueSubTiles);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
args.mode,
|
||||
@@ -243,8 +247,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = true;
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
@@ -272,7 +275,7 @@ public:
|
||||
args.scheduler, typename ProblemShape::UnderlyingProblemShape{}, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles);
|
||||
workspace_size = round_nearest(workspace_size, MinWorkspaceAlignment);
|
||||
|
||||
workspace_size += CollectiveEpilogue::get_workspace_size(args.problem_shape, args.epilogue);
|
||||
workspace_size += CollectiveEpilogue::get_workspace_size(args.problem_shape, args.epilogue, args.hw_info.sm_count);
|
||||
workspace_size = round_nearest(workspace_size, MinWorkspaceAlignment);
|
||||
|
||||
// Get SM count if needed, otherwise use user supplied SM count
|
||||
@@ -298,7 +301,7 @@ public:
|
||||
constexpr uint32_t NumEpilogueSubTiles = CollectiveEpilogue::get_store_pipe_increment(TileShape{});
|
||||
|
||||
status = TileScheduler::template initialize_workspace<typename ProblemShape::UnderlyingProblemShape, ElementAccumulator>(
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, typename ProblemShape::UnderlyingProblemShape{}, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles);
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, typename ProblemShape::UnderlyingProblemShape{}, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles, cuda_adapter);
|
||||
workspace_offset += TileScheduler::template get_workspace_size<typename ProblemShape::UnderlyingProblemShape, ElementAccumulator>(
|
||||
args.scheduler, typename ProblemShape::UnderlyingProblemShape{}, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
@@ -307,10 +310,10 @@ public:
|
||||
}
|
||||
|
||||
status = CollectiveEpilogue::initialize_workspace(args.problem_shape, args.epilogue, workspace_ptr + workspace_offset, stream, cuda_adapter);
|
||||
workspace_offset += CollectiveEpilogue::get_workspace_size(args.problem_shape, args.epilogue);
|
||||
workspace_offset += CollectiveEpilogue::get_workspace_size(args.problem_shape, args.epilogue, args.hw_info.sm_count);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
|
||||
status = CollectiveMainloop::initialize_workspace(args.problem_shape, args.mainloop, workspace_ptr + workspace_offset, stream);
|
||||
status = CollectiveMainloop::initialize_workspace(args.problem_shape, args.mainloop, workspace_ptr + workspace_offset, stream, cuda_adapter);
|
||||
workspace_offset += CollectiveMainloop::get_workspace_size(args.problem_shape, args.mainloop, args.hw_info.sm_count);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
|
||||
@@ -336,7 +339,7 @@ public:
|
||||
}
|
||||
else {
|
||||
grid_shape = TileScheduler::get_grid_shape(params.problem_shape.get_host_problem_shape(), TileShape{}, ClusterShape{}, params.hw_info, args);
|
||||
}
|
||||
}
|
||||
return grid_shape;
|
||||
}
|
||||
|
||||
@@ -361,10 +364,10 @@ public:
|
||||
static_assert(size<0>(TileShape{}) >= 128,
|
||||
"Cooperative kernel requires Tile Size to be greater than or equal to 128 along the M-dimension.");
|
||||
|
||||
static_assert(cute::rank(UnderlyingStrideA{}) == 3, "StrideA must be rank-3: [M, K, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(UnderlyingStrideB{}) == 3, "StrideB must be rank-3: [N, K, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(UnderlyingStrideC{}) == 3, "StrideC must be rank-3: [M, N, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(UnderlyingStrideD{}) == 3, "StrideD must be rank-3: [M, N, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(InternalStrideA{}) == 3, "StrideA must be rank-3: [M, K, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(InternalStrideB{}) == 3, "StrideB must be rank-3: [N, K, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(InternalStrideC{}) == 3, "StrideC must be rank-3: [M, N, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
static_assert(cute::rank(InternalStrideD{}) == 3, "StrideD must be rank-3: [M, N, L]. If batch mode is not needed, set L stride to Int<0>.");
|
||||
|
||||
/* In the Cooperative kernel, Consumer0 and Consumer1 collaborate on the same tile */
|
||||
enum class WarpGroupRole {
|
||||
@@ -406,7 +409,7 @@ public:
|
||||
}
|
||||
mainloop_pipeline_params.is_leader = warp_group_thread_idx == 0;
|
||||
mainloop_pipeline_params.num_consumers = size(TiledMma{});
|
||||
mainloop_pipeline_params.transaction_bytes = CollectiveMainloop::TmaTransactionBytes;
|
||||
mainloop_pipeline_params.transaction_bytes = params.mainloop.tma_transaction_bytes;
|
||||
MainloopPipeline mainloop_pipeline(shared_storage.pipelines.mainloop, mainloop_pipeline_params, ClusterShape{});
|
||||
|
||||
// Epilogue Load pipeline
|
||||
@@ -421,7 +424,9 @@ public:
|
||||
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
||||
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
||||
epi_load_pipeline_params.consumer_arv_count = size(TiledMma{});
|
||||
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
||||
if constexpr (CollectiveEpilogue::RequiresTransactionBytes) {
|
||||
epi_load_pipeline_params.transaction_bytes = params.epilogue.tma_transaction_bytes;
|
||||
}
|
||||
EpiLoadPipeline epi_load_pipeline(shared_storage.pipelines.epi_load, epi_load_pipeline_params);
|
||||
|
||||
// Epilogue Store pipeline
|
||||
@@ -464,18 +469,23 @@ public:
|
||||
auto blk_shape = TileShape{}; // (BLK_M,BLK_N,BLK_K)
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
|
||||
// In a warp specialized kernel, collectives expose data movement and compute operations separately
|
||||
CollectiveMainloop collective_mainloop;
|
||||
CollectiveEpilogue collective_epilogue(params.epilogue, shared_storage.tensors.epilogue);
|
||||
|
||||
// Wait for all thread blocks in the Cluster
|
||||
cluster_wait_fn();
|
||||
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
if (not work_tile_info.is_valid()) {
|
||||
// When problem shapes are only on device, the grid launched may be larger than the total number of blocks across groups
|
||||
return;
|
||||
}
|
||||
|
||||
// Optionally append 1s until problem shape is rank-4 in case it is only rank-3 (MNK)
|
||||
auto problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), Int<1>{});
|
||||
|
||||
// In a warp specialized kernel, collectives expose data movement and compute operations separately
|
||||
CollectiveMainloop collective_mainloop;
|
||||
CollectiveEpilogue collective_epilogue(params.epilogue, shared_storage.tensors.epilogue);
|
||||
|
||||
// Prepare and partition the input tensors. Expects a tuple of tensors where:
|
||||
// get<0>(load_inputs) is the tma tensor A after local tiling so that it has shape (BLK_M,BLK_K,m,k,l)
|
||||
// get<1>(load_inputs) is the tma tensor B after local tiling so that it has shape (BLK_N,BLK_K,n,k,l)
|
||||
@@ -489,16 +499,12 @@ public:
|
||||
// Get pipeline stage increments from tensor shapes
|
||||
auto k_tile_count = size<3>(gA_mkl);
|
||||
|
||||
// Wait for all thread blocks in the Cluster
|
||||
cluster_wait_fn();
|
||||
|
||||
if (warp_group_role == WarpGroupRole::Producer) {
|
||||
cutlass::arch::warpgroup_reg_dealloc<LoadRegisterRequirement>();
|
||||
|
||||
// Mainloop Producer Warp
|
||||
if (producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
int32_t curr_batch = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl)); // Usually just returns work_tile_info.L_idx;
|
||||
int32_t next_batch = curr_batch;
|
||||
int32_t const mock_l_coord = 0;
|
||||
int32_t const sm_idx = blockIdx.x + (blockIdx.y * gridDim.x);
|
||||
int32_t const sm_count = params.hw_info.sm_count;
|
||||
@@ -513,18 +519,19 @@ public:
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
next_batch
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensor replace
|
||||
// Ensure warp is converged before issuing tensormap fence release
|
||||
__syncwarp();
|
||||
// Entire warp must do this (ie its aligned)
|
||||
// Entire warp must do this (i.e. it's aligned)
|
||||
collective_mainloop.tensormaps_cp_fence_release(shared_storage.tensormaps.mainloop, input_tensormaps);
|
||||
}
|
||||
|
||||
bool do_load_order_arrive = true;
|
||||
bool did_batch_change = true;
|
||||
while (work_tile_info.is_valid()) {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -538,7 +545,9 @@ public:
|
||||
auto work_k_tile_start = TileScheduler::get_work_k_tile_start(work_tile_info);
|
||||
auto k_tile_iter = cute::make_coord_iterator(idx2crd(work_k_tile_start, shape<3>(gA_mkl)), shape<3>(gA_mkl));
|
||||
|
||||
collective_mainloop.tensormaps_fence_acquire(input_tensormaps);
|
||||
if (did_batch_change) {
|
||||
collective_mainloop.tensormaps_fence_acquire(input_tensormaps);
|
||||
}
|
||||
|
||||
collective_mainloop.load(
|
||||
params.mainloop,
|
||||
@@ -563,16 +572,17 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
next_batch = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl)); // Usually just returns work_tile_info.L_idx
|
||||
|
||||
if (work_tile_info.is_valid() && next_batch != curr_batch ) {
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
auto next_batch = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl)); // Usually just returns work_tile_info.L_idx
|
||||
did_batch_change = next_batch != curr_batch;
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
curr_batch = next_batch;
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(next_batch), Int<1>{});
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(curr_batch), Int<1>{});
|
||||
}
|
||||
// Purpose of this pipeline state is to make sure TMA loads have finished before doing descriptor updates
|
||||
// Since this state is waiting for loads to finish, it must start in the inverted phase.
|
||||
typename CollectiveMainloop::PipelineState mainloop_pipe_tma_consumer_state =
|
||||
typename CollectiveMainloop::PipelineState mainloop_pipe_tma_consumer_state =
|
||||
{mainloop_pipe_producer_state.index(), !mainloop_pipe_producer_state.phase(), mainloop_pipe_producer_state.count()};
|
||||
mainloop_pipeline.consumer_wait(mainloop_pipe_tma_consumer_state);
|
||||
collective_mainloop.tensormaps_perform_update(
|
||||
@@ -580,13 +590,12 @@ public:
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
next_batch
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensor replace
|
||||
__syncwarp();
|
||||
// Entire warp must do this (ie its aligned)
|
||||
// Entire warp must do this (i.e. it's aligned)
|
||||
collective_mainloop.tensormaps_cp_fence_release(shared_storage.tensormaps.mainloop, input_tensormaps);
|
||||
curr_batch = next_batch;
|
||||
}
|
||||
// Advance the producer state for the last remaining stage that was being waited for above
|
||||
mainloop_pipe_producer_state.advance(1);
|
||||
@@ -598,19 +607,49 @@ public:
|
||||
|
||||
// Epilogue Producer Warp
|
||||
else if (producer_warp_role == ProducerWarpRole::Epilogue && collective_epilogue.is_producer_load_needed()) {
|
||||
int32_t const sm_idx = blockIdx.x + (blockIdx.y * gridDim.x);
|
||||
int32_t const sm_count = params.hw_info.sm_count;
|
||||
|
||||
auto epi_load_tensormap = get<0>(collective_epilogue.load_init(params.epilogue, sm_count, sm_idx));
|
||||
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = true;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_epilogue.tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
work_tile_info.L_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, lane_predicate);
|
||||
}
|
||||
|
||||
load_order_barrier.wait();
|
||||
while (work_tile_info.is_valid()) {
|
||||
if (!TileScheduler::requires_separate_reduction(params.scheduler)) {
|
||||
load_order_barrier.wait();
|
||||
}
|
||||
if (TileScheduler::compute_epilogue(work_tile_info, params.scheduler)) {
|
||||
int32_t curr_batch = work_tile_info.L_idx;
|
||||
|
||||
bool compute_epilogue = TileScheduler::compute_epilogue(work_tile_info, params.scheduler);
|
||||
|
||||
if (compute_epilogue) {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), Int<1>{});
|
||||
}
|
||||
|
||||
// Compute m_coord, n_coord, l_coord with the post-tiled m-shape and n-shape
|
||||
auto m_coord = idx2crd(work_tile_info.M_idx, shape<2>(gA_mkl));
|
||||
auto n_coord = idx2crd(work_tile_info.N_idx, shape<2>(gB_nkl));
|
||||
auto l_coord = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl));
|
||||
auto blk_coord = make_coord(m_coord, n_coord, _, l_coord);
|
||||
|
||||
epi_load_pipe_producer_state =
|
||||
collective_epilogue.load(
|
||||
if (did_batch_change) {
|
||||
collective_epilogue.tensormaps_fence_acquire<IsEpiLoad>(epi_load_tensormap);
|
||||
}
|
||||
|
||||
epi_load_pipe_producer_state = collective_epilogue.load(
|
||||
epi_load_pipeline,
|
||||
epi_load_pipe_producer_state,
|
||||
problem_shape_MNKL,
|
||||
@@ -619,17 +658,40 @@ public:
|
||||
tiled_mma,
|
||||
lane_idx,
|
||||
shared_storage.tensors.epilogue,
|
||||
work_tile_info.reduction_subtile_idx()
|
||||
epi_load_tensormap,
|
||||
work_tile_info.reduction_subtile_idx(),
|
||||
true // return state prior to last advance
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
if (work_tile_info.is_valid()) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), Int<1>{});
|
||||
}
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
// Wait for TMA load to finish before updating
|
||||
typename CollectiveEpilogue::LoadPipelineState epi_load_pipe_tma_consumer_state =
|
||||
{epi_load_pipe_producer_state.index(), !epi_load_pipe_producer_state.phase(), epi_load_pipe_producer_state.count()};
|
||||
|
||||
epi_load_pipeline.consumer_wait(epi_load_pipe_tma_consumer_state);
|
||||
|
||||
collective_epilogue.tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
work_tile_info.L_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, lane_predicate);
|
||||
}
|
||||
|
||||
if(compute_epilogue) {
|
||||
epi_load_pipe_producer_state.advance(1);
|
||||
}
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
@@ -640,9 +702,36 @@ public:
|
||||
else if (warp_group_role == WarpGroupRole::Consumer0 || warp_group_role == WarpGroupRole::Consumer1) {
|
||||
cutlass::arch::warpgroup_reg_alloc<MmaRegisterRequirement>();
|
||||
|
||||
int32_t const sm_idx = blockIdx.x + (blockIdx.y * gridDim.x);
|
||||
int32_t const sm_count = params.hw_info.sm_count;
|
||||
// Do we potentially issue tail arrives for TMA stores, if epilogue load is waiting for it
|
||||
bool do_store_tail = false;
|
||||
// Get a copy of tensormaps
|
||||
auto epi_store_tensormap = get<0>(collective_epilogue.store_init(params.epilogue, sm_count, sm_idx));
|
||||
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = false;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_epilogue.tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
work_tile_info.L_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_store_tensormap, lane_predicate);
|
||||
}
|
||||
|
||||
while (work_tile_info.is_valid()) {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), Int<1>{});
|
||||
}
|
||||
|
||||
int32_t curr_batch = work_tile_info.L_idx;
|
||||
|
||||
// Compute m_coord, n_coord, l_coord with the post-tiled m-shape and n-shape
|
||||
auto m_coord = idx2crd(work_tile_info.M_idx, shape<2>(gA_mkl));
|
||||
auto n_coord = idx2crd(work_tile_info.N_idx, shape<2>(gB_nkl));
|
||||
@@ -683,6 +772,11 @@ public:
|
||||
params.scheduler, work_tile_info, accumulators, NumMmaWarpGroups, consumer_warp_group_idx);
|
||||
|
||||
if (TileScheduler::compute_epilogue(work_tile_info, params.scheduler)) {
|
||||
|
||||
if (did_batch_change) {
|
||||
collective_epilogue.tensormaps_fence_acquire<IsEpiLoad>(epi_store_tensormap);
|
||||
}
|
||||
|
||||
// Epilogue and write to gD
|
||||
auto [epi_load_pipe_consumer_state_next, epi_store_pipe_producer_state_next] =
|
||||
collective_epilogue.store(
|
||||
@@ -697,6 +791,7 @@ public:
|
||||
tiled_mma,
|
||||
mma_thread_idx,
|
||||
shared_storage.tensors.epilogue,
|
||||
epi_store_tensormap,
|
||||
work_tile_info.reduction_subtile_idx()
|
||||
);
|
||||
epi_load_pipe_consumer_state = epi_load_pipe_consumer_state_next;
|
||||
@@ -705,12 +800,22 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
if (work_tile_info.is_valid()) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), Int<1>{});
|
||||
}
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
collective_epilogue.tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
work_tile_info.L_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_store_tensormap, lane_predicate);
|
||||
}
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
if (do_store_tail) {
|
||||
@@ -725,24 +830,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// Kernel helper function to get next work unit
|
||||
CUTLASS_DEVICE
|
||||
typename TileScheduler::WorkTileInfo
|
||||
fetch_next_work(
|
||||
typename TileScheduler::WorkTileInfo& work_tile_info,
|
||||
TileScheduler& scheduler) const {
|
||||
// Check whether we should continue on with the current work unit. If this is the case,
|
||||
// the work unit will have been updated in continue_current_work to reflect the new
|
||||
// tile to be computed.
|
||||
if (scheduler.continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
scheduler.advance_to_next_work();
|
||||
return scheduler.get_current_work();
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -38,7 +38,9 @@
|
||||
#include "cutlass/epilogue/collective/detail.hpp"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
#include "cutlass/gemm/kernel/sm90_tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cute/tensor.hpp"
|
||||
|
||||
@@ -46,19 +48,6 @@
|
||||
|
||||
namespace cutlass::gemm::kernel {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// IF_SWAP_AB<T>::value will be true only if:
|
||||
// class T has member SwapAB and T::SwapAB is true
|
||||
template <typename T, typename = void>
|
||||
struct IF_SWAP_AB { static constexpr bool value = false; };
|
||||
|
||||
template <typename T>
|
||||
struct IF_SWAP_AB <T, void_t<decltype(T::SwapAB)>>
|
||||
{ static constexpr bool value = T::SwapAB; };
|
||||
|
||||
} // namespace
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
@@ -151,7 +140,7 @@ public:
|
||||
to_underlying_arguments(Arguments const& args, void* workspace) {
|
||||
(void) workspace;
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -164,8 +153,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -285,15 +273,13 @@ public:
|
||||
);
|
||||
|
||||
constexpr int BLK_M_RANK = cute::rank<0>(blk_shape);
|
||||
bool m_oob = int(blockIdx.x) >= size<2>(gA_mkl);
|
||||
auto m_max_coord = unwrap(cute::transform(make_seq<BLK_M_RANK>{}, [&](auto i) {
|
||||
return m_oob ? 0 : get<i>(M) - get<0,i>(blk_shape) * get<i>(m_coord);
|
||||
return get<i>(M) - get<0,i>(blk_shape) * get<i>(m_coord);
|
||||
}));
|
||||
|
||||
constexpr int BLK_N_RANK = cute::rank<1>(blk_shape);
|
||||
bool n_oob = int(blockIdx.y) >= size<2>(gB_nkl);
|
||||
auto n_max_coord = unwrap(cute::transform(make_seq<BLK_N_RANK>{}, [&](auto i) {
|
||||
return n_oob ? 0 : get<i>(N) - get<1,i>(blk_shape) * get<i>(n_coord);
|
||||
return get<i>(N) - get<1,i>(blk_shape) * get<i>(n_coord);
|
||||
}));
|
||||
auto residue_mnk = make_tuple(m_max_coord, n_max_coord, Int<0>{});
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ public:
|
||||
to_underlying_arguments(Arguments const& args, void* workspace) {
|
||||
(void) workspace;
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -170,8 +170,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -220,8 +219,11 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
#if defined(__CUDA_ARCH_FEAT_SM90_ALL)
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the WGMMA ISA is arch conditional to sm90a.
|
||||
#if ! defined(__CUDA_ARCH_FEAT_SM90_ALL)
|
||||
#if ! defined(ENABLE_SM90_KERNEL_LEVEL)
|
||||
printf("ERROR : Arch conditional MMA instruction used without targeting sm90a compute capability. Aborting.\n");
|
||||
#else
|
||||
|
||||
@@ -267,7 +269,7 @@ public:
|
||||
}
|
||||
mainloop_pipeline_params.is_leader = warp_group_thread_idx == 0;
|
||||
mainloop_pipeline_params.num_consumers = NumThreadsPerWarpGroup;
|
||||
mainloop_pipeline_params.transaction_bytes = CollectiveMainloop::TmaTransactionBytes;
|
||||
mainloop_pipeline_params.transaction_bytes = params.mainloop.tma_transaction_bytes;
|
||||
MainloopPipeline mainloop_pipeline(shared_storage.pipelines.mainloop, mainloop_pipeline_params, ClusterShape{});
|
||||
|
||||
// Epilogue Load pipeline
|
||||
@@ -282,7 +284,9 @@ public:
|
||||
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
||||
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
||||
epi_load_pipeline_params.consumer_arv_count = NumThreadsPerWarpGroup;
|
||||
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
||||
if constexpr (CollectiveEpilogue::RequiresTransactionBytes) {
|
||||
epi_load_pipeline_params.transaction_bytes = params.epilogue.tma_transaction_bytes;
|
||||
}
|
||||
EpiLoadPipeline epi_load_pipeline(shared_storage.pipelines.epi_load, epi_load_pipeline_params);
|
||||
|
||||
// Epilogue Store pipeline
|
||||
@@ -388,7 +392,7 @@ public:
|
||||
);
|
||||
collective_epilogue.load_tail(epi_load_pipeline, epi_load_pipe_producer_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (warp_group_role == WarpGroupRole::Consumer) {
|
||||
Tensor accumulators = partition_fragment_C(tiled_mma, take<0,2>(blk_shape)); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cute/tensor.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::kernel {
|
||||
@@ -116,14 +117,6 @@ public:
|
||||
|
||||
// Kernel level shared memory storage
|
||||
struct SharedStorage {
|
||||
struct TensorStorage : cute::aligned_struct<128> {
|
||||
using MainloopTensorStorage = typename CollectiveMainloop::TensorStorage;
|
||||
using EpilogueTensorStorage = typename CollectiveEpilogue::TensorStorage;
|
||||
|
||||
MainloopTensorStorage mainloop;
|
||||
EpilogueTensorStorage epilogue;
|
||||
} tensors;
|
||||
|
||||
struct PipelineStorage : cute::aligned_struct<16> {
|
||||
using MainloopPipelineStorage = typename CollectiveMainloop::PipelineStorage;
|
||||
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
||||
@@ -132,6 +125,14 @@ public:
|
||||
alignas(16) EpiLoadPipelineStorage epi_load;
|
||||
alignas(16) typename LoadWarpOrderBarrier::SharedStorage load_order;
|
||||
} pipelines;
|
||||
|
||||
struct TensorStorage : cute::aligned_struct<128> {
|
||||
using MainloopTensorStorage = typename CollectiveMainloop::TensorStorage;
|
||||
using EpilogueTensorStorage = typename CollectiveEpilogue::TensorStorage;
|
||||
|
||||
EpilogueTensorStorage epilogue;
|
||||
MainloopTensorStorage mainloop;
|
||||
} tensors;
|
||||
};
|
||||
|
||||
static constexpr int SharedStorageSize = sizeof(SharedStorage);
|
||||
@@ -168,7 +169,7 @@ public:
|
||||
CUTLASS_TRACE_HOST("to_underlying_arguments():");
|
||||
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -219,8 +220,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -250,7 +250,7 @@ public:
|
||||
}
|
||||
|
||||
static cutlass::Status
|
||||
initialize_workspace(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr,
|
||||
initialize_workspace(Arguments const& args, void* workspace = nullptr, cudaStream_t stream = nullptr,
|
||||
CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
Status status = Status::kSuccess;
|
||||
uint8_t* workspace_ptr = reinterpret_cast<uint8_t*>(workspace);
|
||||
@@ -258,7 +258,7 @@ public:
|
||||
constexpr uint32_t NumEpilogueSubTiles = CollectiveEpilogue::get_store_pipe_increment(TileShape{});
|
||||
|
||||
status = TileScheduler::template initialize_workspace<ProblemShape, ElementAccumulator>(
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles);
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles, cuda_adapter);
|
||||
workspace_offset += TileScheduler::template get_workspace_size<ProblemShape, ElementAccumulator>(
|
||||
args.scheduler, args.problem_shape, args.hw_info, NumMmaWarpGroups, NumEpilogueSubTiles);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
@@ -299,9 +299,12 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
#if defined(__CUDA_ARCH_FEAT_SM90_ALL)
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the WGMMA ISA is arch conditional to sm90a.
|
||||
#if ! defined(__CUDA_ARCH_FEAT_SM90_ALL)
|
||||
printf("ERROR : Arch conditional MMA instruction used without targeting sm90a compute capability. Aborting.\n");
|
||||
#if ! defined(ENABLE_SM90_KERNEL_LEVEL)
|
||||
printf("ERROR : Arch conditional MMA instruction used without targeting appropriate compute capability. Aborting.\n");
|
||||
#else
|
||||
|
||||
// Preconditions
|
||||
@@ -358,7 +361,7 @@ public:
|
||||
}
|
||||
mainloop_pipeline_params.is_leader = warp_group_thread_idx == 0;
|
||||
mainloop_pipeline_params.num_consumers = size(TiledMma{});
|
||||
mainloop_pipeline_params.transaction_bytes = CollectiveMainloop::TmaTransactionBytes;
|
||||
mainloop_pipeline_params.transaction_bytes = params.mainloop.tma_transaction_bytes;
|
||||
MainloopPipeline mainloop_pipeline(shared_storage.pipelines.mainloop, mainloop_pipeline_params, ClusterShape{});
|
||||
|
||||
// Epilogue Load pipeline
|
||||
@@ -373,7 +376,9 @@ public:
|
||||
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
||||
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
||||
epi_load_pipeline_params.consumer_arv_count = size(TiledMma{});
|
||||
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
||||
if constexpr (CollectiveEpilogue::RequiresTransactionBytes) {
|
||||
epi_load_pipeline_params.transaction_bytes = params.epilogue.tma_transaction_bytes;
|
||||
}
|
||||
EpiLoadPipeline epi_load_pipeline(shared_storage.pipelines.epi_load, epi_load_pipeline_params);
|
||||
|
||||
// Epilogue Store pipeline
|
||||
@@ -419,11 +424,10 @@ public:
|
||||
auto blk_shape = TileShape{}; // (BLK_M,BLK_N,BLK_K)
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
|
||||
// In a warp specialized kernel, collectives expose data movement and compute operations separately
|
||||
CollectiveMainloop collective_mainloop;
|
||||
CollectiveEpilogue collective_epilogue(params.epilogue, shared_storage.tensors.epilogue);
|
||||
|
||||
// Prepare and partition the input tensors. Expects a tuple of tensors where:
|
||||
// get<0>(load_inputs) is the tma tensor A after local tiling so that it has shape (BLK_M,BLK_K,m,k,l)
|
||||
@@ -435,21 +439,20 @@ public:
|
||||
Tensor gA_mkl = get<0>(load_inputs);
|
||||
Tensor gB_nkl = get<1>(load_inputs);
|
||||
|
||||
// Get pipeline stage increments from tensor shapes
|
||||
auto k_tile_count = size<3>(gA_mkl);
|
||||
|
||||
// Wait for all thread blocks in the Cluster
|
||||
cluster_wait_fn();
|
||||
|
||||
if (warp_group_role == WarpGroupRole::Producer) {
|
||||
cutlass::arch::warpgroup_reg_dealloc<LoadRegisterRequirement>();
|
||||
|
||||
CollectiveEpilogue collective_epilogue(params.epilogue, shared_storage.tensors.epilogue);
|
||||
|
||||
// Mainloop Producer Warp
|
||||
if (producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
bool do_load_order_arrive = true;
|
||||
while (work_tile_info.is_valid()) {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -485,19 +488,21 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
collective_mainloop.load_tail(mainloop_pipeline, mainloop_pipe_producer_state);
|
||||
|
||||
} // Mainloop Producer Warp End
|
||||
|
||||
// Epilogue Producer Warp
|
||||
else if (producer_warp_role == ProducerWarpRole::Epilogue && collective_epilogue.is_producer_load_needed()) {
|
||||
|
||||
if (!TileScheduler::requires_separate_reduction(params.scheduler) && work_tile_info.is_valid()) {
|
||||
load_order_barrier.wait();
|
||||
}
|
||||
while (work_tile_info.is_valid()) {
|
||||
if (!TileScheduler::requires_separate_reduction(params.scheduler)) {
|
||||
load_order_barrier.wait();
|
||||
}
|
||||
if (TileScheduler::compute_epilogue(work_tile_info, params.scheduler)) {
|
||||
// Compute m_coord, n_coord, l_coord with the post-tiled m-shape and n-shape
|
||||
auto m_coord = idx2crd(work_tile_info.M_idx, shape<2>(gA_mkl));
|
||||
@@ -520,7 +525,7 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
@@ -531,6 +536,8 @@ public:
|
||||
else if (warp_group_role == WarpGroupRole::Consumer0 || warp_group_role == WarpGroupRole::Consumer1) {
|
||||
cutlass::arch::warpgroup_reg_alloc<MmaRegisterRequirement>();
|
||||
|
||||
CollectiveEpilogue collective_epilogue(params.epilogue, shared_storage.tensors.epilogue);
|
||||
|
||||
// Do we potentially issue tail arrives for TMA stores, if epilogue load is waiting for it
|
||||
bool do_store_tail = false;
|
||||
while (work_tile_info.is_valid()) {
|
||||
@@ -596,7 +603,7 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
if (do_store_tail) {
|
||||
@@ -611,24 +618,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// Kernel helper function to get next work unit
|
||||
CUTLASS_DEVICE
|
||||
typename TileScheduler::WorkTileInfo
|
||||
fetch_next_work(
|
||||
typename TileScheduler::WorkTileInfo& work_tile_info,
|
||||
TileScheduler& scheduler) const {
|
||||
// Check whether we should continue on with the current work unit. If this is the case,
|
||||
// the work unit will have been updated in continue_current_work to reflect the new
|
||||
// tile to be computed.
|
||||
if (scheduler.continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
scheduler.advance_to_next_work();
|
||||
return scheduler.get_current_work();
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/kernel/sm90_tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
@@ -119,27 +121,31 @@ public:
|
||||
static constexpr uint32_t StagesPerMathWarpGroup = 2;
|
||||
using MathWarpGroupOrderBarrier = cutlass::OrderedSequenceBarrier<
|
||||
StagesPerMathWarpGroup, NumMmaWarpGroups>;
|
||||
using MathWarpGroupOrderBarrierSharedStorage =
|
||||
cutlass::PipelineDetail::OrderedSequenceBarrierSharedStorage<
|
||||
MathWarpGroupOrderBarrier::SequenceDepth,
|
||||
MathWarpGroupOrderBarrier::SequenceLength>;
|
||||
|
||||
// Kernel level shared memory storage
|
||||
struct SharedStorage {
|
||||
struct TensorStorage : cute::aligned_struct<128> {
|
||||
using MainloopTensorStorage = typename CollectiveMainloop::TensorStorage;
|
||||
using EpilogueTensorStorage = typename CollectiveEpilogue::TensorStorage;
|
||||
|
||||
MainloopTensorStorage mainloop;
|
||||
EpilogueTensorStorage epilogue;
|
||||
} tensors;
|
||||
|
||||
struct PipelineStorage : cute::aligned_struct<16> {
|
||||
using MainloopPipelineStorage = typename CollectiveMainloop::PipelineStorage;
|
||||
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
||||
using MathWarpGroupOrderBarrierStorage = typename MathWarpGroupOrderBarrier::SharedStorage;
|
||||
using MathWarpGroupOrderBarrierStorage = MathWarpGroupOrderBarrierSharedStorage;
|
||||
|
||||
alignas(16) MainloopPipelineStorage mainloop;
|
||||
alignas(16) EpiLoadPipelineStorage epi_load;
|
||||
alignas(16) MathWarpGroupOrderBarrierStorage math_wg_order;
|
||||
alignas(16) typename LoadWarpOrderBarrier::SharedStorage load_order;
|
||||
} pipelines;
|
||||
|
||||
struct TensorStorage : cute::aligned_struct<128> {
|
||||
using MainloopTensorStorage = typename CollectiveMainloop::TensorStorage;
|
||||
using EpilogueTensorStorage = typename CollectiveEpilogue::TensorStorage;
|
||||
|
||||
EpilogueTensorStorage epilogue;
|
||||
MainloopTensorStorage mainloop;
|
||||
} tensors;
|
||||
};
|
||||
|
||||
static constexpr int SharedStorageSize = sizeof(SharedStorage);
|
||||
@@ -176,7 +182,7 @@ public:
|
||||
|
||||
(void) workspace;
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -219,8 +225,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -256,7 +261,7 @@ public:
|
||||
size_t workspace_offset = 0;
|
||||
|
||||
status = TileScheduler::template initialize_workspace<ProblemShape, ElementAccumulator>(
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups);
|
||||
args.scheduler, workspace_ptr + workspace_offset, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups, 1, cuda_adapter);
|
||||
workspace_offset += TileScheduler::template get_workspace_size<ProblemShape, ElementAccumulator>(
|
||||
args.scheduler, args.problem_shape, args.hw_info, NumMmaWarpGroups);
|
||||
workspace_offset = round_nearest(workspace_offset, MinWorkspaceAlignment);
|
||||
@@ -350,7 +355,7 @@ public:
|
||||
}
|
||||
mainloop_pipeline_params.is_leader = warp_group_thread_idx == 0;
|
||||
mainloop_pipeline_params.num_consumers = NumThreadsPerWarpGroup;
|
||||
mainloop_pipeline_params.transaction_bytes = CollectiveMainloop::TmaTransactionBytes;
|
||||
mainloop_pipeline_params.transaction_bytes = params.mainloop.tma_transaction_bytes;
|
||||
MainloopPipeline mainloop_pipeline(shared_storage.pipelines.mainloop, mainloop_pipeline_params, ClusterShape{});
|
||||
|
||||
// Epilogue Load pipeline
|
||||
@@ -365,7 +370,9 @@ public:
|
||||
epi_load_pipeline_params.dst_blockid = cute::block_rank_in_cluster();
|
||||
epi_load_pipeline_params.producer_arv_count = NumThreadsPerWarp;
|
||||
epi_load_pipeline_params.consumer_arv_count = NumThreadsPerWarpGroup;
|
||||
epi_load_pipeline_params.transaction_bytes = CollectiveEpilogue::TmaTransactionBytes;
|
||||
if constexpr (CollectiveEpilogue::RequiresTransactionBytes) {
|
||||
epi_load_pipeline_params.transaction_bytes = params.epilogue.tma_transaction_bytes;
|
||||
}
|
||||
EpiLoadPipeline epi_load_pipeline(shared_storage.pipelines.epi_load, epi_load_pipeline_params);
|
||||
|
||||
// Epilogue Store pipeline
|
||||
@@ -446,7 +453,7 @@ public:
|
||||
epi_load_pipe_consumer_state.advance(c_tile_count);
|
||||
epi_store_pipe_producer_state.advance(d_tile_count);
|
||||
}
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
|
||||
// Wait for all thread blocks in the Cluster
|
||||
cluster_wait_fn();
|
||||
@@ -493,10 +500,12 @@ public:
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
collective_mainloop.load_tail(mainloop_pipeline, mainloop_pipe_producer_state);
|
||||
|
||||
} // Mainloop Producer Warp End
|
||||
|
||||
// Epilogue Producer Warp
|
||||
else if (producer_warp_role == ProducerWarpRole::Epilogue && collective_epilogue.is_producer_load_needed()) {
|
||||
|
||||
load_order_barrier.wait();
|
||||
while (work_tile_info.is_valid()) {
|
||||
// Compute m_coord, n_coord, l_coord with the post-tiled m-shape and n-shape
|
||||
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
to_underlying_arguments(Arguments const& args, void* workspace) {
|
||||
(void) workspace;
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -174,8 +174,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
|
||||
@@ -164,7 +164,7 @@ public:
|
||||
CUTLASS_TRACE_HOST("to_underlying_arguments():");
|
||||
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -195,8 +195,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -225,7 +224,7 @@ public:
|
||||
CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
TileScheduler t;
|
||||
return t.template initialize_workspace<ProblemShape, ElementAccumulator>(
|
||||
args.scheduler, workspace, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups);
|
||||
args.scheduler, workspace, stream, args.problem_shape, args.hw_info, NumMmaWarpGroups, 1, cuda_adapter);
|
||||
}
|
||||
|
||||
// Computes the kernel launch grid shape based on runtime parameters
|
||||
@@ -340,7 +339,7 @@ public:
|
||||
Tensor gB_nkl = local_tile(mB_nkl, blk_shape, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
|
||||
// In a warp specialized kernel, collectives expose data movement and compute operations separately
|
||||
CollectiveMainloop collective_mainloop;
|
||||
@@ -402,7 +401,7 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
@@ -478,7 +477,7 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
work_tile_info = fetch_next_work(work_tile_info, scheduler);
|
||||
work_tile_info = scheduler.fetch_next_work(work_tile_info);
|
||||
} // Scheduler work fetch loop
|
||||
|
||||
if (do_store_tail) {
|
||||
@@ -493,24 +492,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// Kernel helper function to get next work unit
|
||||
CUTLASS_DEVICE
|
||||
typename TileScheduler::WorkTileInfo
|
||||
fetch_next_work(
|
||||
typename TileScheduler::WorkTileInfo& work_tile_info,
|
||||
TileScheduler& scheduler) const {
|
||||
// Check whether we should continue on with the current work unit. If this is the case,
|
||||
// the work unit will have been updated in continue_current_work to reflect the new
|
||||
// tile to be computed.
|
||||
if (scheduler.continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
scheduler.advance_to_next_work();
|
||||
return scheduler.get_current_work();
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_decl.h"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
@@ -175,7 +176,7 @@ public:
|
||||
|
||||
(void) workspace;
|
||||
auto problem_shape = args.problem_shape;
|
||||
if constexpr (detail::IF_SWAP_AB<CollectiveMainloop>::value) {
|
||||
if constexpr (detail::Has_SwapAB_v<CollectiveMainloop>) {
|
||||
// swap M/N
|
||||
get<0>(problem_shape) = get<1>(args.problem_shape);
|
||||
get<1>(problem_shape) = get<0>(args.problem_shape);
|
||||
@@ -206,8 +207,7 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE static
|
||||
bool
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
bool implementable = (args.mode == GemmUniversalMode::kGemm) or
|
||||
(args.mode == GemmUniversalMode::kBatched && cute::rank(ProblemShape{}) == 4);
|
||||
@@ -367,7 +367,7 @@ public:
|
||||
epi_load_pipe_consumer_state.advance(c_tile_count);
|
||||
epi_store_pipe_producer_state.advance(d_tile_count);
|
||||
}
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
|
||||
// In a warp specialized kernel, collectives expose data movement and compute operations separately
|
||||
CollectiveMainloop collective_mainloop;
|
||||
|
||||
@@ -128,10 +128,22 @@ public:
|
||||
template <class ProblemShape, class ElementAccumulator>
|
||||
static cutlass::Status
|
||||
initialize_workspace(Arguments const&, void*, cudaStream_t, ProblemShape, KernelHardwareInfo const&,
|
||||
uint32_t, const uint32_t = 1) {
|
||||
uint32_t, const uint32_t = 1, CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
return Status::kSuccess;
|
||||
}
|
||||
|
||||
// Kernel helper function to get next work tile
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
fetch_next_work(WorkTileInfo work_tile_info) {
|
||||
if (continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
advance_to_next_work();
|
||||
return get_current_work();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -204,7 +204,6 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
return true;
|
||||
@@ -408,7 +407,7 @@ public:
|
||||
template <class ProblemShape, class ElementAccumulator>
|
||||
static cutlass::Status
|
||||
initialize_workspace(Arguments const&, void*, cudaStream_t, ProblemShape, KernelHardwareInfo const&,
|
||||
uint32_t, const uint32_t = 1) {
|
||||
uint32_t, const uint32_t = 1, CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
return Status::kSuccess;
|
||||
}
|
||||
|
||||
@@ -480,6 +479,27 @@ public:
|
||||
requires_separate_reduction(Params const& params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kernel helper function to get next work tile
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
fetch_next_work(WorkTileInfo work_tile_info) {
|
||||
if (continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
advance_to_next_work();
|
||||
return get_current_work();
|
||||
}
|
||||
|
||||
// Returns the initial work tile info that will be computed over
|
||||
template <class ClusterShape>
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
initial_work_tile_info(ClusterShape) {
|
||||
return get_current_work();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace cutlass::gemm::kernel::detail
|
||||
|
||||
@@ -226,7 +226,6 @@ public:
|
||||
return params;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
// Split count > 1 is only valid for heuristic and split-K decomposition modes
|
||||
@@ -263,7 +262,7 @@ public:
|
||||
// for the fact that we have splits_ peers per output tile, we multiply this
|
||||
// value by splits_. For stream-K, this multiplication ends up being a no-op
|
||||
// because splits_ is set to 1 for stream-K.
|
||||
if(linear_idx >= (params.units_per_problem_ * params.splits_ + params.separate_reduction_units_)) {
|
||||
if(linear_idx >= (params.units_per_problem_ * params.divmod_splits_.divisor + params.separate_reduction_units_)) {
|
||||
// Invalid work. Return an empty result.
|
||||
return WorkTileInfo::invalid_work_tile();
|
||||
}
|
||||
@@ -423,7 +422,7 @@ public:
|
||||
using BlockStripedReduceT = BlockStripedReduce<BarrierManager::ThreadCount, AccumulatorArrayT>;
|
||||
|
||||
AccumulatorArrayT* reduction_workspace_array = reinterpret_cast<AccumulatorArrayT*>(group_reduction_workspace);
|
||||
AccumulatorArrayT* accumulator_array = reinterpret_cast<AccumulatorArrayT*>(&accumulators);
|
||||
AccumulatorArrayT* accumulator_array = reinterpret_cast<AccumulatorArrayT*>(accumulators.data());
|
||||
|
||||
int barrier_group_thread_idx = threadIdx.x % BarrierManager::ThreadCount;
|
||||
|
||||
@@ -434,7 +433,7 @@ public:
|
||||
// note that, in the split-K case, the units_per_problem_ member of Params will be
|
||||
// the total number of output tiles.
|
||||
uint32_t reduction_tiles = 0;
|
||||
if (params.splits_ > 1) {
|
||||
if (params.divmod_splits_.divisor > 1) {
|
||||
reduction_tiles = params.units_per_problem_;
|
||||
}
|
||||
else if (params.requires_separate_reduction()) {
|
||||
@@ -583,7 +582,8 @@ public:
|
||||
ProblemShape const& problem_shape,
|
||||
KernelHardwareInfo const& hw_info,
|
||||
uint32_t mma_warp_groups,
|
||||
const uint32_t epilogue_subtile = 1) {
|
||||
const uint32_t epilogue_subtile = 1,
|
||||
CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
|
||||
|
||||
@@ -608,7 +608,9 @@ public:
|
||||
mma_warp_groups,
|
||||
sizeof_bits<BarrierType>::value,
|
||||
sizeof_bits<ElementAccumulator>::value,
|
||||
epilogue_subtile
|
||||
epilogue_subtile,
|
||||
1,
|
||||
cuda_adapter
|
||||
);
|
||||
}
|
||||
|
||||
@@ -625,6 +627,25 @@ public:
|
||||
return work_tile_info.K_idx;
|
||||
}
|
||||
|
||||
// Kernel helper function to get next work tile
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
fetch_next_work(WorkTileInfo work_tile_info) {
|
||||
if (continue_current_work(work_tile_info)) {
|
||||
return work_tile_info;
|
||||
}
|
||||
|
||||
advance_to_next_work();
|
||||
return get_current_work();
|
||||
}
|
||||
|
||||
// Returns the initial work tile info that will be computed over
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
initial_work_tile_info(ClusterShape) {
|
||||
return get_current_work();
|
||||
}
|
||||
|
||||
private:
|
||||
// Sets the current stream-K work to compute within work_tile_info. If new_unit is true, work_tile_info
|
||||
// is populated as a new unit of work. Otherwise, state existing in work_tile_info (e.g., remaining
|
||||
@@ -636,8 +657,11 @@ private:
|
||||
uint64_t linear_idx,
|
||||
WorkTileInfo& work_tile_info) {
|
||||
|
||||
auto [cta_m_in_cluster_, cta_n_in_cluster_, _] = cute::block_id_in_cluster();
|
||||
uint64_t cta_m_in_cluster = static_cast<uint64_t>(cta_m_in_cluster_);
|
||||
uint64_t cta_n_in_cluster = static_cast<uint64_t>(cta_n_in_cluster_);
|
||||
uint64_t output_tile_id = linear_idx;
|
||||
if (linear_idx >= params.units_per_problem_ * params.splits_) {
|
||||
if (linear_idx >= params.units_per_problem_ * params.divmod_splits_.divisor) {
|
||||
// Separate-reduction work
|
||||
auto cluster_size = params.get_cluster_size();
|
||||
// Divide up the linearized separate reduction units into clusters
|
||||
@@ -649,7 +673,7 @@ private:
|
||||
|
||||
work_tile_info.setup_separate_reduction(epi_subtile_idx);
|
||||
}
|
||||
else if (linear_idx >= params.sk_units_ && params.splits_ == 1) {
|
||||
else if (linear_idx >= params.sk_units_ && params.divmod_splits_.divisor == 1) {
|
||||
// Data-parallel work
|
||||
output_tile_id = linear_idx - params.sk_units_ + params.sk_tiles_;
|
||||
work_tile_info.K_idx = 0;
|
||||
@@ -697,11 +721,11 @@ private:
|
||||
uint64_t split;
|
||||
params.divmod_clusters_mnl_(split, cluster_linear_work_idx, cluster_linear_work_idx);
|
||||
|
||||
bool is_split_k = params.splits_ > 1;
|
||||
bool is_split_k = params.divmod_splits_.divisor > 1;
|
||||
auto big_unit_cmp_lhs = is_split_k ? split : cluster_linear_work_idx;
|
||||
auto big_unit_cmp_rhs = is_split_k ? params.big_units_ : big_units_in_group;
|
||||
auto linear_idx_mult = is_split_k ? params.divmod_tiles_per_output_tile_.divisor : k_tiles_per_unit_in_group;
|
||||
auto k_tiles_per_split = is_split_k ? params.k_tiles_per_sk_unit_ : k_tiles_per_unit_in_group;
|
||||
auto k_tiles_per_split = is_split_k ? params.divmod_k_tiles_per_sk_unit_.divisor : k_tiles_per_unit_in_group;
|
||||
|
||||
// Determine the starting k iteration computed by this stream-K work unit
|
||||
uint32_t unit_iter_start = (linear_idx_mult * cluster_linear_work_idx) +
|
||||
@@ -744,6 +768,15 @@ private:
|
||||
unit_iter_start += adjustment_tiles;
|
||||
k_tiles_in_my_split -= adjustment_tiles;
|
||||
}
|
||||
else if (params.ktile_start_alignment_count == 2 && start_tile_k_tile % 2 != 0) {
|
||||
// ktile for each SM start from even number
|
||||
// If start from odd number ktile within the output tile
|
||||
// now start at the ktile one before my initial ktile start (take one ktile from prev sm)
|
||||
// if end on odd number ktile within the output tile
|
||||
// now end at ktile that one before my ktile end (give one ktile to next sm)
|
||||
unit_iter_start -= 1;
|
||||
k_tiles_in_my_split += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (work_tile_info.k_tile_count == 0) {
|
||||
@@ -773,6 +806,14 @@ private:
|
||||
// Adjust our work to take on these K tiles.
|
||||
k_tiles_in_my_split += (params.divmod_tiles_per_output_tile_.divisor - end_tile_k_tile);
|
||||
}
|
||||
else if (params.ktile_start_alignment_count == 2 && end_tile_k_tile % 2 != 0) {
|
||||
// ktile for each SM start from even number
|
||||
// If start from odd number ktile within the output tile
|
||||
// now start at the ktile one before my initial ktile start (take one ktile from prev sm)
|
||||
// If end on odd number ktile within the output tile,
|
||||
// now end at ktile that one before my ktile end (give one ktile to next sm)
|
||||
k_tiles_in_my_split -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
work_tile_info.k_tile_remaining = k_tiles_in_my_split;
|
||||
@@ -801,8 +842,6 @@ private:
|
||||
// Bring the linearized tile ID back into the space of tiles, rather than clusters
|
||||
output_tile_id *= params.get_cluster_size();
|
||||
|
||||
auto [cta_m_in_cluster, cta_n_in_cluster, _] = cute::block_id_in_cluster();
|
||||
|
||||
// The final linearized tile ID is in units of the cluster dimension over which we rasterize.
|
||||
if (params.raster_order_ == RasterOrder::AlongN) {
|
||||
output_tile_id += cta_n_in_cluster * params.divmod_cluster_shape_minor_.divisor;
|
||||
@@ -853,7 +892,7 @@ private:
|
||||
auto tile_idx_in_cluster_path = params.div_cluster_size(tile_idx);
|
||||
auto start_k_tile = params.divmod_tiles_per_output_tile_.divisor * tile_idx_in_cluster_path;
|
||||
auto end_k_tile = start_k_tile + params.divmod_tiles_per_output_tile_.divisor - 1;
|
||||
auto big_unit_k_tiles = params.big_units_ * (params.k_tiles_per_sk_unit_ + 1);
|
||||
auto big_unit_k_tiles = params.big_units_ * (params.divmod_k_tiles_per_sk_unit_.divisor + 1);
|
||||
|
||||
auto adjust_unit = [&](uint32_t k_tile, uint32_t unit_idx, uint32_t k_tiles_per_unit) {
|
||||
auto unit_k_start = unit_idx * k_tiles_per_unit;
|
||||
@@ -881,16 +920,14 @@ private:
|
||||
auto find_unit = [&](uint32_t k_tile) {
|
||||
if (k_tile < big_unit_k_tiles) {
|
||||
// The tile is within the "big unit range"
|
||||
auto k_tiles_per_unit = params.k_tiles_per_sk_unit_ + 1;
|
||||
auto unit_idx = k_tile / k_tiles_per_unit;
|
||||
return static_cast<uint64_t>(adjust_unit(k_tile, unit_idx, k_tiles_per_unit));
|
||||
auto unit_idx = params.divmod_k_tiles_per_sk_big_unit_.divide(k_tile);
|
||||
return static_cast<uint64_t>(adjust_unit(k_tile, unit_idx, params.divmod_k_tiles_per_sk_big_unit_.divisor));
|
||||
}
|
||||
else {
|
||||
// The tile is after the "big unit range." Account for this by finding the "normal unit"
|
||||
// that it belongs to, and then offsetting by the number of big units
|
||||
auto k_tiles_per_unit = params.k_tiles_per_sk_unit_;
|
||||
auto unit_idx = ((k_tile - big_unit_k_tiles) / params.k_tiles_per_sk_unit_) + (params.big_units_);
|
||||
return static_cast<uint64_t>(adjust_unit(k_tile, unit_idx, k_tiles_per_unit));
|
||||
auto unit_idx = params.divmod_k_tiles_per_sk_unit_.divide(k_tile - big_unit_k_tiles) + params.big_units_;
|
||||
return static_cast<uint64_t>(adjust_unit(k_tile, unit_idx, params.divmod_k_tiles_per_sk_unit_.divisor));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
CUTLASS_HOST_DEVICE
|
||||
static bool
|
||||
can_implement(Arguments const& args) {
|
||||
return true;
|
||||
return args.max_swizzle_size >= 1;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
@@ -206,18 +206,18 @@ public:
|
||||
int32_t log_swizzle_size,
|
||||
RasterOrder raster_order) {
|
||||
|
||||
auto [cta_m_in_cluster, cta_n_in_cluster, _] = cute::block_id_in_cluster();
|
||||
|
||||
uint64_t minor_work_idx, major_work_idx, cluster_minor_offset;
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
minor_work_idx = static_cast<uint64_t>(tile_m);
|
||||
major_work_idx = static_cast<uint64_t>(tile_n);
|
||||
cluster_minor_offset = cta_m_in_cluster;
|
||||
uint64_t cluster_m = divmod_cluster_shape_minor.divide(tile_m) * divmod_cluster_shape_minor.divisor;
|
||||
cluster_minor_offset = tile_m - cluster_m;
|
||||
}
|
||||
else {
|
||||
major_work_idx = static_cast<uint64_t>(tile_m);
|
||||
minor_work_idx = static_cast<uint64_t>(tile_n);
|
||||
cluster_minor_offset = cta_n_in_cluster;
|
||||
uint64_t cluster_n = divmod_cluster_shape_minor.divide(tile_n) * divmod_cluster_shape_minor.divisor;
|
||||
cluster_minor_offset = tile_n - cluster_n;
|
||||
}
|
||||
|
||||
uint64_t cluster_idx_minor, cluster_idx_major, cluster_major_offset;
|
||||
@@ -248,21 +248,6 @@ public:
|
||||
cta_m, cta_n
|
||||
);
|
||||
}
|
||||
// Kernel helper function to get next work ID
|
||||
template <class WorkIdPipeline, class WorkIdPipelineState>
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
fetch_next_work(
|
||||
WorkTileInfo work_tile_info,
|
||||
WorkIdPipeline& work_id_pipeline,
|
||||
WorkIdPipelineState work_id_pipe_consumer_state) {
|
||||
WorkTileInfo new_work_tile_info;
|
||||
advance_to_next_work();
|
||||
new_work_tile_info = get_current_work();
|
||||
|
||||
// Return true to indicate that the WorkID pipeline state should be advanced
|
||||
return cute::make_tuple(new_work_tile_info, true);
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
\brief Utilities for selecting default tile schedulers
|
||||
*/
|
||||
|
||||
#include "cutlass/arch/arch.h"
|
||||
#include "cutlass/detail/dependent_false.hpp"
|
||||
#include "cutlass/gemm/kernel/sm90_tile_scheduler.hpp"
|
||||
#include "cutlass/gemm/kernel/sm90_tile_scheduler_stream_k.hpp"
|
||||
|
||||
@@ -168,7 +168,8 @@ struct PersistentTileSchedulerSm90Params {
|
||||
KernelHardwareInfo hw_info,
|
||||
int max_swizzle_size,
|
||||
RasterOrderOptions raster_order_option,
|
||||
bool truncate_by_problem_size=true) {
|
||||
bool truncate_by_problem_size=true
|
||||
) {
|
||||
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape, cta_shape, cluster_shape);
|
||||
return get_grid_shape(
|
||||
@@ -192,7 +193,8 @@ struct PersistentTileSchedulerSm90Params {
|
||||
KernelHardwareInfo hw_info,
|
||||
int max_swizzle_size,
|
||||
RasterOrderOptions raster_order_option,
|
||||
bool truncate_by_problem_size=true) {
|
||||
bool truncate_by_problem_size=true
|
||||
) {
|
||||
|
||||
int const sm_count = hw_info.sm_count;
|
||||
|
||||
@@ -238,6 +240,7 @@ struct PersistentTileSchedulerSm90Params {
|
||||
}
|
||||
}
|
||||
else {
|
||||
int cta_per_device = sm_count;
|
||||
/*
|
||||
* Optimal grid size calculation is based on
|
||||
* GH100: 8 GPCs, 72 TPCs (9 TPCs/GPC), 2 SMs/TPC, 144 SMs per full GPU
|
||||
@@ -248,15 +251,16 @@ struct PersistentTileSchedulerSm90Params {
|
||||
auto cluster_size = cluster_shape.m() * cluster_shape.n();
|
||||
int const min_num_gpc = sm_count < max_sm_per_gpc ? 1 : sm_count / max_sm_per_gpc;
|
||||
int const max_cta_occupancy_per_gpc = max_sm_per_gpc - (max_sm_per_gpc % cluster_size);
|
||||
int cta_per_device = min_num_gpc * max_cta_occupancy_per_gpc;
|
||||
cta_per_device = min_num_gpc * max_cta_occupancy_per_gpc;
|
||||
|
||||
// The calculation below allows for larger grid size launch for different GPUs.
|
||||
int const num_gpc_residual = sm_count < max_sm_per_gpc ? 0 : sm_count % max_sm_per_gpc;
|
||||
int const max_cta_occupancy_per_residual_gpc = num_gpc_residual - (num_gpc_residual % cluster_size);
|
||||
cta_per_device += max_cta_occupancy_per_residual_gpc;
|
||||
|
||||
cta_per_device = sm_count < cta_per_device ? sm_count : cta_per_device;
|
||||
|
||||
if (sm_count < cta_per_device) {
|
||||
cta_per_device = sm_count;
|
||||
}
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
launch_grid.y = possibly_truncate(
|
||||
cta_per_device / cluster_shape.m(),
|
||||
@@ -420,7 +424,7 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
// The splitting factor to be used in a split-K decomposition of the problem.
|
||||
// If this is set to a value greater than 1, stream-K decomposition logic
|
||||
// is bypassed in favor of a split-K decomposition.
|
||||
uint32_t splits_ = 1;
|
||||
FastDivmod divmod_splits_{};
|
||||
|
||||
// Number of stream-K or split-K work units that compute an extra k iteration.
|
||||
// This is done to handle residuals in dividing up the k iteration space.
|
||||
@@ -442,7 +446,10 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
|
||||
// Number of tiled k iterations computed by each stream-K work unit. This
|
||||
// can potentially cover more than one output tile.
|
||||
uint32_t k_tiles_per_sk_unit_ = 0;
|
||||
FastDivmod divmod_k_tiles_per_sk_unit_{};
|
||||
// Number of tiled k iterations computed by each "big" stream-K units, which
|
||||
// processes one more K chunk than a "normal" stream-K unit.
|
||||
FastDivmod divmod_k_tiles_per_sk_big_unit_{};
|
||||
|
||||
// Strategy to use when reducing between collaborating CTAs
|
||||
ReductionMode reduction_mode_ = ReductionMode::Deterministic;
|
||||
@@ -459,6 +466,9 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
// Maximum number of groups of stream-K units
|
||||
static constexpr uint32_t max_sk_groups_ = 8u;
|
||||
|
||||
// ktile start from even for each cta
|
||||
uint32_t ktile_start_alignment_count { 1u };
|
||||
|
||||
// Divides dividend by the cluster size
|
||||
CUTLASS_HOST_DEVICE
|
||||
uint64_t
|
||||
@@ -585,6 +595,14 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
splits = k_tiles_per_output_tile;
|
||||
}
|
||||
|
||||
// If splits == k_tiles_per_output_tiles, there will be one k_tile per cta
|
||||
// and this violate k_tile start from even requirements. Thus we need to
|
||||
// reduce the number of splits.
|
||||
if (ktile_start_alignment_count > 1u &&
|
||||
static_cast<decltype(k_tiles_per_output_tile)>(splits) == k_tiles_per_output_tile) {
|
||||
splits = k_tiles_per_output_tile / ktile_start_alignment_count;
|
||||
}
|
||||
|
||||
set_params_basic(
|
||||
underlying_params,
|
||||
problem_blocks_m,
|
||||
@@ -686,7 +704,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
auto sk_splits_too_small = [&](uint32_t g) {
|
||||
// Check whether the number of K tiles computed per stream-K unit is less
|
||||
// than min_iters_per_sk_unit_
|
||||
auto total_sk_k_tiles = (sk_tiles / g) * k_tiles_per_output_tile;
|
||||
auto total_sk_cluster_tiles = (sk_cluster_tiles / g) * cluster_size;
|
||||
auto total_sk_k_tiles = total_sk_cluster_tiles * k_tiles_per_output_tile;
|
||||
auto k_tiles_per_sk_unit = total_sk_k_tiles / (sk_units / g);
|
||||
return k_tiles_per_sk_unit < min_iters_per_sk_unit_;
|
||||
};
|
||||
@@ -725,13 +744,12 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
// sk_tiles = (waves <= 2) ? total_tiles : (sm_count + (total_tiles % sm_count))
|
||||
// Both total_tiles and sm_count are multiples of cluster size due to padding added
|
||||
// prior to kernel launch.
|
||||
uint64_t sk_clustered_tiles = sk_tiles / cluster_size;
|
||||
uint64_t sk_clustered_tiles_per_group = sk_clustered_tiles / groups;
|
||||
uint64_t sk_tiles_per_group = sk_clustered_tiles_per_group * cluster_size;
|
||||
uint64_t sk_cluster_tiles_per_group = sk_cluster_tiles / groups;
|
||||
uint64_t sk_tiles_per_group = sk_cluster_tiles_per_group * cluster_size;
|
||||
|
||||
// Groups that will process an extra stream-K tile cluster. These differ from "big_units," which
|
||||
// are stream-K units within a group that process an extra K chunk.
|
||||
uint64_t sk_big_groups = sk_clustered_tiles % groups;
|
||||
uint64_t sk_big_groups = sk_cluster_tiles % groups;
|
||||
|
||||
uint64_t k_tiles_per_group = k_tiles_per_output_tile * sk_tiles_per_group;
|
||||
|
||||
@@ -777,7 +795,7 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
// This setting ensures that the use of this divmod for stream-K decompositions
|
||||
// is essentially a no-op.
|
||||
divmod_clusters_mnl_ = FastDivmodU64(sk_units / cluster_size);
|
||||
splits_ = 1;
|
||||
divmod_splits_ = FastDivmod(1);
|
||||
log_swizzle_size_ = underlying_params.log_swizzle_size_;
|
||||
units_per_problem_ = static_cast<uint32_t>(dp_units + sk_units);
|
||||
raster_order_ = underlying_params.raster_order_;
|
||||
@@ -790,7 +808,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
reduction_workspace_ = reduction_workspace;
|
||||
sk_tiles_ = sk_tiles;
|
||||
sk_units_ = static_cast<uint32_t>(sk_units);
|
||||
k_tiles_per_sk_unit_ = static_cast<uint32_t>(k_tiles_per_sk_unit);
|
||||
divmod_k_tiles_per_sk_unit_ = FastDivmod(static_cast<uint32_t>(k_tiles_per_sk_unit));
|
||||
divmod_k_tiles_per_sk_big_unit_ = FastDivmod(static_cast<uint32_t>(k_tiles_per_sk_unit + 1));
|
||||
reduction_mode_ = reduction_mode;
|
||||
divmod_epilogue_subtile_ = FastDivmodU64(epilogue_subtile);
|
||||
separate_reduction_units_ = reduction_units;
|
||||
@@ -923,19 +942,19 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
|
||||
// Calculates the size of the workspace needed for holding reduction barriers
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int
|
||||
static size_t
|
||||
get_barrier_workspace_size(uint64_t num_tiles, uint32_t mma_warp_groups, uint32_t barrier_bits) {
|
||||
auto workspace_bits = num_tiles * mma_warp_groups * barrier_bits;
|
||||
return round_up_to_l2_alignment(bits_to_bytes(static_cast<int>(workspace_bits)));
|
||||
size_t workspace_bits = num_tiles * static_cast<size_t>(mma_warp_groups) * static_cast<size_t>(barrier_bits);
|
||||
return round_up_to_l2_alignment(bits_to_bytes<size_t>(workspace_bits));
|
||||
}
|
||||
|
||||
// Calculates the size of the workspace needed for holding partial outputs from splits
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int
|
||||
static size_t
|
||||
get_reduction_workspace_size(uint64_t num_tiles, GemmCoord tile_shape, uint32_t accumulator_bits, uint32_t num_accumulator_mtxs = 1) {
|
||||
auto output_tile_size = tile_shape.m() * tile_shape.n();
|
||||
auto workspace_bits = accumulator_bits * output_tile_size * num_tiles * num_accumulator_mtxs;
|
||||
return round_up_to_l2_alignment(bits_to_bytes(static_cast<int>(workspace_bits)));
|
||||
size_t output_tile_size = tile_shape.m() * tile_shape.n();
|
||||
size_t workspace_bits = accumulator_bits * output_tile_size * num_tiles * num_accumulator_mtxs;
|
||||
return round_up_to_l2_alignment(bits_to_bytes<size_t>(workspace_bits));
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
@@ -945,8 +964,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
uint32_t k_tiles_per_output_tile,
|
||||
GemmCoord tile_shape,
|
||||
GemmCoord cluster_shape,
|
||||
int& barrier_workspace_size,
|
||||
int& reduction_workspace_size,
|
||||
size_t& barrier_workspace_size,
|
||||
size_t& reduction_workspace_size,
|
||||
KernelHardwareInfo const& hw_info,
|
||||
int splits,
|
||||
int max_swizzle,
|
||||
@@ -970,8 +989,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
barrier_workspace_size = 0;
|
||||
reduction_workspace_size = 0;
|
||||
}
|
||||
else if (decomposition_mode == DecompositionMode::SplitK ||
|
||||
(decomposition_mode == DecompositionMode::Heuristic && splits > 1)) {
|
||||
else if (splits > 1 &&
|
||||
(decomposition_mode == DecompositionMode::SplitK || decomposition_mode == DecompositionMode::Heuristic)) {
|
||||
// Basic split-K variant requires workspace for all output tiles
|
||||
barrier_workspace_size = get_barrier_workspace_size(output_tiles, mma_warp_groups, barrier_bits);
|
||||
reduction_workspace_size = get_reduction_workspace_size(output_tiles, tile_shape, accumulator_bits, num_accumulator_mtxs);
|
||||
@@ -1094,8 +1113,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
uint32_t epilogue_subtile = 1,
|
||||
uint32_t num_accumulator_mtxs = 1) {
|
||||
|
||||
int barrier_workspace_size = 0;
|
||||
int reduction_workspace_size = 0;
|
||||
size_t barrier_workspace_size = 0;
|
||||
size_t reduction_workspace_size = 0;
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
get_workspace_component_sizes(
|
||||
@@ -1138,7 +1157,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
uint32_t mma_warp_groups,
|
||||
uint32_t barrier_bits,
|
||||
uint32_t element_accumulator_bits,
|
||||
uint32_t epilogue_subtile) {
|
||||
uint32_t epilogue_subtile,
|
||||
CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
|
||||
dim3 problem_blocks = UnderlyingParams::get_tiled_cta_shape_mnl(problem_shape, tile_shape, cluster_shape);
|
||||
uint32_t k_tiles_per_output_tile = (problem_shape.k() + tile_shape.k() - 1) / tile_shape.k();
|
||||
@@ -1158,7 +1178,9 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
mma_warp_groups,
|
||||
barrier_bits,
|
||||
element_accumulator_bits,
|
||||
epilogue_subtile
|
||||
epilogue_subtile,
|
||||
1,
|
||||
cuda_adapter
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1182,11 +1204,12 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
uint32_t barrier_bits,
|
||||
uint32_t element_accumulator_bits,
|
||||
uint32_t epilogue_subtile = 1,
|
||||
uint32_t num_accumulator_mtxs = 1) {
|
||||
uint32_t num_accumulator_mtxs = 1,
|
||||
CudaHostAdapter* cuda_adapter = nullptr) {
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
int barrier_workspace_size = 0;
|
||||
int reduction_workspace_size = 0;
|
||||
uint64_t barrier_workspace_size = 0;
|
||||
uint64_t reduction_workspace_size = 0;
|
||||
|
||||
get_workspace_component_sizes(
|
||||
problem_blocks,
|
||||
@@ -1215,7 +1238,7 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
// Only the barrier workspace needs to be cleared for stream-K.
|
||||
// Barrier workspace follows reduction workspace.
|
||||
uint8_t* barrier_workspace = reinterpret_cast<uint8_t*>(workspace) + reduction_workspace_size;
|
||||
return zero_workspace(static_cast<void*>(barrier_workspace), barrier_workspace_size, stream);
|
||||
return zero_workspace(static_cast<void*>(barrier_workspace), barrier_workspace_size, stream, cuda_adapter);
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
@@ -1240,7 +1263,7 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
divmod_sk_groups_ = FastDivmodU64(1u);
|
||||
auto cluster_size = underlying_params.divmod_cluster_shape_major_.divisor * underlying_params.divmod_cluster_shape_minor_.divisor;
|
||||
divmod_clusters_mnl_ = FastDivmodU64((blocks_m * blocks_n * blocks_l) / cluster_size);
|
||||
splits_ = splits;
|
||||
divmod_splits_ = FastDivmod(splits);
|
||||
divmod_cluster_blk_major_ = underlying_params.divmod_cluster_blk_major_;
|
||||
log_swizzle_size_ = underlying_params.log_swizzle_size_;
|
||||
units_per_problem_ = blocks_m * blocks_n * blocks_l;
|
||||
@@ -1248,7 +1271,8 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
big_units_ = k_tiles_per_output_tile % splits;
|
||||
reduction_workspace_ = reduction_workspace;
|
||||
reduction_mode_ = reduction_mode;
|
||||
k_tiles_per_sk_unit_ = k_tiles_per_output_tile / splits;
|
||||
divmod_k_tiles_per_sk_unit_ = FastDivmod(k_tiles_per_output_tile / splits);
|
||||
divmod_k_tiles_per_sk_big_unit_ = FastDivmod(k_tiles_per_output_tile / splits + 1);
|
||||
|
||||
// No stream-K work is performed for "basic" data-parallel and split-K decompositions
|
||||
sk_tiles_ = 0;
|
||||
@@ -1260,9 +1284,9 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
private:
|
||||
// Round up number of bytes to the nearest multiple of L2 cache line alignment
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int
|
||||
round_up_to_l2_alignment(int bytes) {
|
||||
constexpr static uint32_t L2CacheLineSizeBytes = 128;
|
||||
static size_t
|
||||
round_up_to_l2_alignment(size_t bytes) {
|
||||
constexpr size_t L2CacheLineSizeBytes = 128u;
|
||||
return (bytes + L2CacheLineSizeBytes - 1) / L2CacheLineSizeBytes * L2CacheLineSizeBytes;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -191,16 +191,34 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// Default Operator
|
||||
using Operator = Operator_;
|
||||
|
||||
// Warp thread arrangement
|
||||
static int const kWarpThreadArrangementContiguousA =
|
||||
platform::min(Shape::kM / (kAccessSizeInBits / sizeof_bits<ElementA>::value), 8);
|
||||
|
||||
static int const kWarpThreadArrangementStridedA =
|
||||
kWarpSize / kWarpThreadArrangementContiguousA;
|
||||
|
||||
static int const kWarpThreadArrangementContiguousB =
|
||||
platform::min(Shape::kN / (kAccessSizeInBits / sizeof_bits<ElementB>::value), 8);
|
||||
|
||||
static int const kWarpThreadArrangementStridedB =
|
||||
kWarpSize / kWarpThreadArrangementContiguousB;
|
||||
|
||||
//
|
||||
// Shared memory layouts
|
||||
//
|
||||
static int const Crosswise_A = platform::min(int(128 / sizeof(ElementA)),
|
||||
Shape::kM);
|
||||
|
||||
using SmemLayoutA = layout::ColumnMajorTensorOpMultiplicandCongruous<
|
||||
sizeof_bits<ElementA>::value, int(128 / sizeof(ElementA))>;
|
||||
sizeof_bits<ElementA>::value, Crosswise_A>;
|
||||
|
||||
// Shared memory layout
|
||||
static int const Crosswise_B = platform::min(int(128 / sizeof(ElementB)),
|
||||
Shape::kN);
|
||||
|
||||
using SmemLayoutB = layout::RowMajorTensorOpMultiplicandCongruous<
|
||||
sizeof_bits<ElementB>::value, int(128 / sizeof(ElementB))>;
|
||||
sizeof_bits<ElementB>::value, Crosswise_B>;
|
||||
|
||||
//
|
||||
// Iterators to write to shared memory
|
||||
@@ -209,7 +227,8 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// ThreadMap of iterator A
|
||||
using IteratorThreadMapA = transform::PitchLinearWarpRakedThreadMap<
|
||||
layout::PitchLinearShape<Shape::kM, Shape::kK / kSparse>, kThreads,
|
||||
layout::PitchLinearShape<8, 4>,
|
||||
layout::PitchLinearShape<kWarpThreadArrangementContiguousA,
|
||||
kWarpThreadArrangementStridedA>,
|
||||
kAccessSizeInBits / sizeof_bits<ElementA>::value>;
|
||||
|
||||
/// Shared memory iterator to A operand
|
||||
@@ -220,7 +239,8 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// ThreadMap of iterator B
|
||||
using IteratorThreadMapB = transform::PitchLinearWarpRakedThreadMap<
|
||||
layout::PitchLinearShape<Shape::kN, Shape::kK>, kThreads,
|
||||
layout::PitchLinearShape<8, 4>,
|
||||
layout::PitchLinearShape<kWarpThreadArrangementContiguousB,
|
||||
kWarpThreadArrangementStridedB>,
|
||||
kAccessSizeInBits / sizeof_bits<ElementB>::value>;
|
||||
|
||||
/// Shared memory iterator to B operand
|
||||
@@ -547,6 +567,16 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// Default Operator
|
||||
using Operator = Operator_;
|
||||
|
||||
// Warp thread arrangement
|
||||
static int const Crosswise_A = platform::min(int(128 / sizeof(ElementA)),
|
||||
Shape::kM);
|
||||
|
||||
static int const kWarpThreadArrangementContiguousA =
|
||||
platform::min(Shape::kM / (kAccessSizeInBits / sizeof_bits<ElementA>::value), 8);
|
||||
|
||||
static int const kWarpThreadArrangementStridedA =
|
||||
kWarpSize / kWarpThreadArrangementContiguousA;
|
||||
|
||||
// Warp thread arrangement
|
||||
// crosswise cannot be larger than 1024 bit.
|
||||
static int const kCrosswiseB =
|
||||
@@ -565,7 +595,7 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
//
|
||||
|
||||
using SmemLayoutA = layout::ColumnMajorTensorOpMultiplicandCongruous<
|
||||
sizeof_bits<ElementA>::value, int(128 / sizeof(ElementA))>;
|
||||
sizeof_bits<ElementA>::value, Crosswise_A>;
|
||||
|
||||
// Shared memory layout
|
||||
using SmemLayoutB = layout::ColumnMajorTensorOpMultiplicandCrosswise<
|
||||
@@ -578,7 +608,8 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// ThreadMap of iterator A
|
||||
using IteratorThreadMapA = transform::PitchLinearWarpRakedThreadMap<
|
||||
layout::PitchLinearShape<Shape::kM, Shape::kK / kSparse>, kThreads,
|
||||
layout::PitchLinearShape<8, 4>,
|
||||
layout::PitchLinearShape<kWarpThreadArrangementContiguousA,
|
||||
kWarpThreadArrangementStridedA>,
|
||||
kAccessSizeInBits / sizeof_bits<ElementA>::value>;
|
||||
|
||||
/// Shared memory iterator to A operand
|
||||
@@ -734,6 +765,16 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
static int const kWarpThreadArrangementStridedA =
|
||||
kWarpSize / kWarpThreadArrangementContiguousA;
|
||||
|
||||
static int const kWarpThreadArrangementContiguousB =
|
||||
platform::min(Shape::kN / (kAccessSizeInBits / sizeof_bits<ElementB>::value), 8);
|
||||
|
||||
static int const kWarpThreadArrangementStridedB =
|
||||
kWarpSize / kWarpThreadArrangementContiguousB;
|
||||
|
||||
static int const Crosswise_B = platform::min(int(128 / sizeof(ElementB)),
|
||||
Shape::kN);
|
||||
|
||||
|
||||
//
|
||||
// Shared memory layouts
|
||||
//
|
||||
@@ -743,7 +784,7 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
|
||||
// Shared memory layout
|
||||
using SmemLayoutB = layout::RowMajorTensorOpMultiplicandCongruous<
|
||||
sizeof_bits<ElementB>::value, int(128 / sizeof(ElementB))>;
|
||||
sizeof_bits<ElementB>::value, Crosswise_B>;
|
||||
|
||||
//
|
||||
// Iterators to write to shared memory
|
||||
@@ -764,7 +805,8 @@ struct DefaultSparseMmaCore<Shape_, WarpShape_, InstructionShape_, ElementA_,
|
||||
/// ThreadMap of iterator B
|
||||
using IteratorThreadMapB = transform::PitchLinearWarpRakedThreadMap<
|
||||
layout::PitchLinearShape<Shape::kN, Shape::kK>, kThreads,
|
||||
layout::PitchLinearShape<8, 4>,
|
||||
layout::PitchLinearShape<kWarpThreadArrangementContiguousB,
|
||||
kWarpThreadArrangementStridedB>,
|
||||
kAccessSizeInBits / sizeof_bits<ElementB>::value>;
|
||||
|
||||
/// Shared memory iterator to B operand
|
||||
|
||||
@@ -1040,6 +1040,15 @@ public:
|
||||
partition_contiguous_idx = (lane_id % Layout::kFactor);
|
||||
access_contiguous_idx = (quad_quad + i * 2) ^ (lane_in_quad_pair / Layout::kFactor);
|
||||
access_strided_idx = (lane_in_quad_quad / Layout::kFactor);
|
||||
} else if (Policy::LdsmShape::kContiguous == 1) {
|
||||
// Matrix multiply 16832.SP B
|
||||
// Q0
|
||||
// Q1
|
||||
// Q2
|
||||
// Q3
|
||||
partition_contiguous_idx = (lane_id % Layout::kFactor);
|
||||
access_contiguous_idx = (lane_in_quad_pair / Layout::kFactor) ^ i;
|
||||
access_strided_idx = lane_id / Layout::kFactor;
|
||||
}
|
||||
|
||||
int access_contiguous =
|
||||
@@ -1432,7 +1441,21 @@ public:
|
||||
access_contiguous_idx =
|
||||
((lane_in_pair * 2 + quad_quad) ^
|
||||
access_strided_idx);
|
||||
}
|
||||
} else if (Policy::LdsmShape::kContiguous == 1) {
|
||||
// Matrix multiply 16832.SP B
|
||||
// Q0
|
||||
// Q1
|
||||
// Q2
|
||||
// Q3
|
||||
int factor_in_partition =
|
||||
(Layout::PartitionShape::kContiguous * Layout::kFactor /
|
||||
Layout::TileShape::kContiguous);
|
||||
|
||||
partition_contiguous_idx = lane_in_quad / factor_in_partition;
|
||||
access_contiguous_idx = ((lane_in_pair * factor_in_partition) ^
|
||||
(lane_in_quad_quad / Layout::kFactor) ^ i);
|
||||
access_strided_idx = lane_id / Layout::kFactor;
|
||||
}
|
||||
|
||||
int access_contiguous =
|
||||
partition_contiguous_idx * Layout::PartitionShape::kContiguous +
|
||||
|
||||
Reference in New Issue
Block a user