cutlass 3.9 update (#2255)
* cutlass 3.9 update * rebase * fixes out of shared memory for blockwise Blackwell * doc format * fix issue 2253 * disable host ref by default * fix sm120 smem capacity --------- Co-authored-by: yuzhai <yuzhai@nvidia.com> Co-authored-by: Haicheng Wu <haichengw@nvidia.com>
This commit is contained in:
co-authored by
yuzhai
Haicheng Wu
parent
8e345c5c5b
commit
331a1f5b3f
@@ -60,16 +60,18 @@ sm100_compute_stage_count_or_override_fast_fp32(StageCountAutoCarveout<carveout_
|
||||
static_assert(CtaN <= 128, "Can't support CtaN>128 tiles");
|
||||
constexpr int CtaK = get<2>(CtaTileShape_MNK{});
|
||||
using AtomThrID = typename TiledMma::AtomThrID;
|
||||
constexpr int TmemColumns = 512;
|
||||
|
||||
// Detect 2x2 TMEM layout
|
||||
constexpr int TmemAccWordsPerDP = (CtaM == 64 && size(AtomThrID{}) == 2) ? CtaN/2 : CtaN;
|
||||
constexpr int TmemAWordsPerDP = ComplexComponent * NumComputeMtxs * CtaK / 2;
|
||||
constexpr bool IsAComputeinTmem = UmmaMajorA == cute::UMMA::Major::K && !cute::is_base_of_v<KernelTmaWarpSpecializedFastFP32SmemSm100, BuilderScheduleTag>;
|
||||
constexpr bool IsAComputeinSmem = !IsAComputeinTmem;
|
||||
constexpr int AccumulatorStageCount = (IsAComputeinTmem) ? (((TmemAccWordsPerDP * ComplexComponent == 128) ? 2 : 3) * ComplexComponent) : (512 / TmemAccWordsPerDP);
|
||||
constexpr int AccumulatorStageCount = (IsAComputeinTmem) ? (((TmemAccWordsPerDP * ComplexComponent == 128) ? 2 : 3) * ComplexComponent) : (TmemColumns / TmemAccWordsPerDP);
|
||||
|
||||
constexpr int SmemCapacityAfterMma2AccumCarveout = CapacityBytes - (carveout_bytes + AccumulatorStageCount * 32);
|
||||
|
||||
constexpr int TmemInAStageCount_Potential = (IsAComputeinTmem) ? (512 - AccumulatorStageCount * TmemAccWordsPerDP) / TmemAWordsPerDP : 10000;
|
||||
constexpr int TmemInAStageCount_Potential = (IsAComputeinTmem) ? (TmemColumns - AccumulatorStageCount * TmemAccWordsPerDP) / TmemAWordsPerDP : 10000;
|
||||
|
||||
constexpr auto load2transform_pipeline_bytes = sizeof(typename cutlass::PipelineTmaTransformAsync<1>::SharedStorage);
|
||||
constexpr auto a_bits = cute::sizeof_bits_v<float> * ComplexComponent;
|
||||
|
||||
@@ -235,9 +235,11 @@ struct CollectiveBuilder<
|
||||
|
||||
static constexpr int MMA_N = cute::size<1>(TileShape_MNK{});
|
||||
static constexpr uint32_t AccumulatorPipelineStageCount = (MMA_N == 256) ? 1 : 2;
|
||||
// Grouped GEMM (where Stride type is Stride*) does not use CLC based scheduler.
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = 1;
|
||||
static constexpr bool IsArrayOfPointersGemm = cute::is_base_of_v<KernelSchedulePtrArrayBlockScaledGemmSm100, BuilderScheduleTag>;
|
||||
// Grouped GEMM(where Stride type is Stride*) uses specific static tile scheduler.
|
||||
static constexpr bool IsGroupGemm = !cute::is_same_v<StrideA, InternalStrideA>;
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = cute::conditional_return<IsGroupGemm>(8, 1);
|
||||
|
||||
static constexpr uint32_t KernelSmemCarveout = detail::Sm100DenseGemmTmaUmmaCarveout<
|
||||
ClusterShape_MNK,
|
||||
AccumulatorPipelineStageCount,
|
||||
|
||||
@@ -49,9 +49,8 @@ template<
|
||||
class ElementScalar,
|
||||
class ScaleShapeMNK,
|
||||
class TileShapeMNK,
|
||||
class MainloopPipelineStorage,
|
||||
class TransformLoadPipelineStorage,
|
||||
class TransformPipelineStorage,
|
||||
class MainloopABPipelineStorage,
|
||||
class MainloopSFPipelineStorage,
|
||||
int stages
|
||||
>
|
||||
constexpr int
|
||||
@@ -67,9 +66,8 @@ template<
|
||||
class ElementScalar,
|
||||
class ScaleShapeMNK,
|
||||
class TileShapeMNK,
|
||||
class MainloopPipelineStorage,
|
||||
class TransformLoadPipelineStorage,
|
||||
class TransformPipelineStorage,
|
||||
class MainloopABPipelineStorage,
|
||||
class MainloopSFPipelineStorage,
|
||||
int stages
|
||||
>
|
||||
constexpr int
|
||||
@@ -85,9 +83,8 @@ template<
|
||||
class ElementScalar,
|
||||
class ScaleShapeMNK,
|
||||
class TileShapeMNK,
|
||||
class MainloopPipelineStorage,
|
||||
class TransformLoadPipelineStorage,
|
||||
class TransformPipelineStorage,
|
||||
class MainloopABPipelineStorage,
|
||||
class MainloopSFPipelineStorage,
|
||||
int carveout_bytes>
|
||||
constexpr int
|
||||
sm100_compute_stage_count_or_override_blockwise(StageCountAutoCarveout<carveout_bytes> stage_count) {
|
||||
@@ -96,18 +93,20 @@ sm100_compute_stage_count_or_override_blockwise(StageCountAutoCarveout<carveout_
|
||||
// Each stage include (CollectiveMma::SharedStorage)
|
||||
// 1. smem for A and smem for B (CollectiveMma::SharedStorage::TensorStorage)
|
||||
// 2. one of each of the pipelines
|
||||
constexpr auto pipeline_bytes = sizeof(MainloopPipelineStorage) +
|
||||
sizeof(TransformLoadPipelineStorage) + sizeof(TransformPipelineStorage);
|
||||
constexpr auto pipeline_bytes = sizeof(MainloopABPipelineStorage) +
|
||||
sizeof(MainloopSFPipelineStorage);
|
||||
|
||||
constexpr auto a_bits = cute::sizeof_bits_v<ElementA>;
|
||||
constexpr auto b_bits = cute::sizeof_bits_v<ElementB>;
|
||||
constexpr auto scale_bits = cute::sizeof_bits_v<ElementScalar>;
|
||||
|
||||
constexpr int stage_bytes =
|
||||
cutlass::bits_to_bytes(a_bits * size<0>(TileShapeMNK{}) * size<2>(TileShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(b_bits * size<1>(TileShapeMNK{}) * size<2>(TileShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(scale_bits * size<0>(ScaleShapeMNK{}) * size<2>(ScaleShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(scale_bits * size<1>(ScaleShapeMNK{}) * size<2>(ScaleShapeMNK{})) +
|
||||
cutlass::round_nearest(
|
||||
cutlass::bits_to_bytes(a_bits * size<0>(TileShapeMNK{}) * size<2>(TileShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(b_bits * size<1>(TileShapeMNK{}) * size<2>(TileShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(scale_bits * size<0>(ScaleShapeMNK{}) * size<2>(ScaleShapeMNK{})) +
|
||||
cutlass::bits_to_bytes(scale_bits * size<1>(ScaleShapeMNK{}) * size<2>(ScaleShapeMNK{})),
|
||||
128) +
|
||||
static_cast<int>(pipeline_bytes);
|
||||
|
||||
return (CapacityBytes - carveout_bytes) / stage_bytes;
|
||||
@@ -369,9 +368,8 @@ struct CollectiveBuilder<
|
||||
static constexpr int Sm100ReducedSmemCapacityBytes = cutlass::gemm::collective::detail::sm100_smem_capacity_bytes - KernelSmemCarveout;
|
||||
|
||||
using SmemTileShape = cute::Shape<BlockTileA_M, BlockTileB_N, BlockTileA_K>;
|
||||
using MainloopPipelineStorage = typename cutlass::PipelineTmaUmmaAsync<1>::SharedStorage;
|
||||
using TransformLoadPipelineStorage = typename cutlass::PipelineAsync<1>::SharedStorage;
|
||||
using TransformPipelineStorage = typename cutlass::PipelineUmmaAsync<1>::SharedStorage;
|
||||
using MainloopABPipelineStorage = typename cutlass::PipelineTmaUmmaAsync<1>::SharedStorage;
|
||||
using MainloopSFPipelineStorage = typename cutlass::PipelineAsync<1>::SharedStorage;
|
||||
|
||||
static constexpr int ScaleGranularityM = size<0,0>(cute::remove_pointer_t<GmemLayoutSFATag>{});
|
||||
static constexpr int ScaleGranularityN = size<0,0>(cute::remove_pointer_t<GmemLayoutSFBTag>{});
|
||||
@@ -398,8 +396,8 @@ struct CollectiveBuilder<
|
||||
|
||||
static constexpr int PipelineStages = cutlass::gemm::collective::detail::sm100_compute_stage_count_or_override_blockwise<
|
||||
Sm100ReducedSmemCapacityBytes, ElementAMma_SmemAllocType, ElementBMma_SmemAllocType,
|
||||
ElementAccumulator, ScaleTileShape, SmemTileShape, MainloopPipelineStorage,
|
||||
TransformLoadPipelineStorage, TransformPipelineStorage>(StageCountType{});
|
||||
ElementAccumulator, ScaleTileShape, SmemTileShape, MainloopABPipelineStorage,
|
||||
MainloopSFPipelineStorage>(StageCountType{});
|
||||
static_assert(PipelineStages > 0, "Smem usage is too high. Can't create any SMEM buffers for A, B, and scales.");
|
||||
|
||||
using DispatchPolicy = cute::conditional_t<
|
||||
|
||||
@@ -264,10 +264,11 @@ struct CollectiveBuilder<
|
||||
// Calculate scheduler pipeline stages. Having one more stage than the accumulator allows more latency hiding.
|
||||
using StrideA = cutlass::gemm::TagToStrideA_t<GmemLayoutATag>;
|
||||
using InternalStrideA = cute::remove_pointer_t<StrideA>;
|
||||
// Grouped GEMM (where Stride type is Stride*) does not use CLC based scheduler.
|
||||
// SchedulerPipelineStageCount could be set to zero for Grouped GEMM, but we shouldn't define CLC Pipeline's barrier arrays of size zero.
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = 1;
|
||||
static constexpr bool IsArrayOfPointersGemm = (cute::is_base_of_v<KernelScheduleSm100PtrArrayDenseGemm, BuilderScheduleTag>);
|
||||
// Grouped GEMM(where Stride type is Stride*) uses specific static tile scheduler.
|
||||
static constexpr bool IsGroupGemm = !cute::is_same_v<StrideA, InternalStrideA>;
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = cute::conditional_return<IsGroupGemm>(8, 1);
|
||||
|
||||
static constexpr uint32_t KernelSmemCarveout = detail::Sm100DenseGemmTmaUmmaCarveout<
|
||||
ClusterShape_MNK,
|
||||
AccumulatorPipelineStageCount,
|
||||
|
||||
@@ -69,6 +69,8 @@ struct CollectiveBuilder<
|
||||
(cute::is_base_of_v<KernelScheduleBlockScaledGemmSm120, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_same_v<KernelScheduleAuto, BuilderScheduleTag>)
|
||||
&&
|
||||
// Alignment check
|
||||
@@ -90,6 +92,7 @@ struct CollectiveBuilder<
|
||||
|
||||
static constexpr cute::UMMA::Major UmmaMajorA = cutlass::gemm::collective::detail::tag_to_umma_major_A<GmemLayoutATag>();
|
||||
static constexpr cute::UMMA::Major UmmaMajorB = cutlass::gemm::collective::detail::tag_to_umma_major_B<GmemLayoutBTag>();
|
||||
static_assert((UmmaMajorA == UMMA::Major::K && UmmaMajorB == UMMA::Major::K), "Only TN layout is supported.");
|
||||
|
||||
static_assert(cute::is_static_v<TileShape_MNK>, "TileShape has to be static");
|
||||
static_assert(cute::is_static_v<ClusterShape_MNK>, "Cluster has to be static");
|
||||
@@ -109,7 +112,8 @@ struct CollectiveBuilder<
|
||||
using PermTileK = cute::conditional_t<(UseMxf8f6f4
|
||||
), _32, _64>;
|
||||
|
||||
static constexpr bool IsCooperative = !cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, BuilderScheduleTag>;
|
||||
static constexpr bool IsCooperative = !(cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, BuilderScheduleTag>);
|
||||
// Data type used by MMA instruction
|
||||
using ElementAMma = decltype(cutlass::gemm::collective::detail::sm1xx_kernel_input_element_to_mma_input_element<ElementA>());
|
||||
using ElementBMma = decltype(cutlass::gemm::collective::detail::sm1xx_kernel_input_element_to_mma_input_element<ElementB>());
|
||||
@@ -210,17 +214,50 @@ struct CollectiveBuilder<
|
||||
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = 3;
|
||||
|
||||
using DispatchPolicy = MainloopSm120TmaWarpSpecializedBlockScaled<PipelineStages,
|
||||
using StrideA = cutlass::gemm::TagToStrideA_t<GmemLayoutATag>;
|
||||
using StrideB = cutlass::gemm::TagToStrideB_t<GmemLayoutBTag>;
|
||||
using InternalStrideA = cute::remove_pointer_t<StrideA>;
|
||||
using InternalStrideB = cute::remove_pointer_t<StrideB>;
|
||||
using InternalLayoutSFA = decltype(Sm1xxBlkScaledConfig::deduce_layoutSFA());
|
||||
using InternalLayoutSFB = decltype(Sm1xxBlkScaledConfig::deduce_layoutSFB());
|
||||
using LayoutSFA = cute::conditional_t<cute::is_same_v<InternalStrideA, StrideA>, InternalLayoutSFA, InternalLayoutSFA *>;
|
||||
using LayoutSFB = cute::conditional_t<cute::is_same_v<InternalStrideB, StrideB>, InternalLayoutSFB, InternalLayoutSFB *>;
|
||||
using StridePairA = decltype(cute::make_tuple(StrideA{}, LayoutSFA{}));
|
||||
using StridePairB = decltype(cute::make_tuple(StrideB{}, LayoutSFB{}));
|
||||
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<InternalStrideA, StrideA>;
|
||||
static_assert(!IsGroupedGemmKernel ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelScheduleAuto, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, BuilderScheduleTag>,
|
||||
"Invalid builder schedule tag for grouped GEMM");
|
||||
|
||||
using KernelSchedule = cute::conditional_t<IsGroupedGemmKernel,
|
||||
// PtrArray
|
||||
cute::conditional_t<IsCooperative,
|
||||
KernelPtrArrayTmaWarpSpecializedCooperativeBlockScaledSm120<SchedulerPipelineStageCount>,
|
||||
KernelPtrArrayTmaWarpSpecializedPingpongBlockScaledSm120<SchedulerPipelineStageCount>>,
|
||||
// Non-PtrArray
|
||||
cute::conditional_t<IsCooperative,
|
||||
KernelTmaWarpSpecializedCooperativeBlockScaledSm120<SchedulerPipelineStageCount>,
|
||||
KernelTmaWarpSpecializedPingpongBlockScaledSm120<SchedulerPipelineStageCount>>>;
|
||||
|
||||
using DispatchPolicy = cute::conditional_t<IsGroupedGemmKernel,
|
||||
MainloopSm120ArrayTmaWarpSpecializedBlockScaled<PipelineStages,
|
||||
SchedulerPipelineStageCount,
|
||||
ClusterShape_MNK,
|
||||
BuilderScheduleTag>;
|
||||
KernelSchedule>,
|
||||
MainloopSm120TmaWarpSpecializedBlockScaled<PipelineStages,
|
||||
SchedulerPipelineStageCount,
|
||||
ClusterShape_MNK,
|
||||
KernelSchedule>>;
|
||||
|
||||
static_assert(cute::is_base_of_v<KernelTmaWarpSpecializedCooperative, typename DispatchPolicy::Schedule> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, typename DispatchPolicy::Schedule>,
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, typename DispatchPolicy::Schedule> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, typename DispatchPolicy::Schedule> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, typename DispatchPolicy::Schedule>,
|
||||
"Unsupported kernel schedule by this collective mainloop dispatch policy.");
|
||||
|
||||
using StridePairA = decltype(cute::make_tuple(cutlass::gemm::TagToStrideA_t<GmemLayoutATag>{}, Sm1xxBlkScaledConfig::deduce_layoutSFA()));
|
||||
using StridePairB = decltype(cute::make_tuple(cutlass::gemm::TagToStrideB_t<GmemLayoutBTag>{}, Sm1xxBlkScaledConfig::deduce_layoutSFB()));
|
||||
|
||||
using CollectiveOp = CollectiveMma<
|
||||
DispatchPolicy,
|
||||
TileShape_MNK,
|
||||
|
||||
@@ -70,6 +70,8 @@ struct CollectiveBuilder<
|
||||
(cute::is_base_of_v<KernelScheduleSm120DenseGemm, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_same_v<KernelScheduleAuto, BuilderScheduleTag>) &&
|
||||
// Alignment check
|
||||
detail::sm1xx_gemm_is_aligned<ElementA, AlignmentA, ElementB, AlignmentB, BuilderScheduleTag>()>>
|
||||
@@ -79,9 +81,11 @@ struct CollectiveBuilder<
|
||||
"SM120 TmaWarpSpecialized builder currently only supports F8F6F4 MMA.");
|
||||
static_assert(cute::is_static_v<TileShape_MNK>, "TileShape has to be static");
|
||||
static_assert(cute::is_static_v<ClusterShape_MNK>, "Cluster has to be static");
|
||||
static_assert(cute::size(ClusterShape_MNK{}) == Int<1>{}, "no programmatic multicast on this arch");
|
||||
|
||||
static constexpr cute::GMMA::Major GmmaMajorA = detail::gmma_rs_tag_to_major_A<GmemLayoutATag>();
|
||||
static constexpr cute::GMMA::Major GmmaMajorB = detail::gmma_rs_tag_to_major_B<GmemLayoutBTag>();
|
||||
static constexpr cute::UMMA::Major UmmaMajorA = cutlass::gemm::collective::detail::tag_to_umma_major_A<GmemLayoutATag>();
|
||||
static constexpr cute::UMMA::Major UmmaMajorB = cutlass::gemm::collective::detail::tag_to_umma_major_B<GmemLayoutBTag>();
|
||||
static_assert((UmmaMajorA == UMMA::Major::K && UmmaMajorB == UMMA::Major::K), "Only TN layout is supported.");
|
||||
|
||||
using PermTileM = decltype(cute::min(size<0>(TileShape_MNK{}), _128{}));
|
||||
using PermTileN = decltype(cute::min(size<1>(TileShape_MNK{}), _32{}));
|
||||
@@ -127,10 +131,24 @@ struct CollectiveBuilder<
|
||||
detail::sm120_smem_capacity_bytes, SmemAllocTypeA,
|
||||
SmemAllocTypeB, TileShape_MNK, MainloopPipelineStorage>(StageCountType{});
|
||||
static constexpr uint32_t SchedulerPipelineStageCount = 2;
|
||||
|
||||
|
||||
static constexpr bool IsPtrArrayKernel = cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, BuilderScheduleTag> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, BuilderScheduleTag>;
|
||||
static_assert(!IsPtrArrayKernel, "PtrArray kernel is not supported for this collective builder.");
|
||||
|
||||
using KernelSchedule = cute::conditional_t<IsCooperative,
|
||||
KernelTmaWarpSpecializedCooperativeSm120<SchedulerPipelineStageCount>,
|
||||
KernelTmaWarpSpecializedPingpongSm120<SchedulerPipelineStageCount>>;
|
||||
|
||||
using DispatchPolicy = MainloopSm120TmaWarpSpecialized<PipelineStages,
|
||||
SchedulerPipelineStageCount,
|
||||
ClusterShape_MNK,
|
||||
BuilderScheduleTag>;
|
||||
SchedulerPipelineStageCount,
|
||||
ClusterShape_MNK,
|
||||
KernelSchedule>;
|
||||
|
||||
static_assert(cute::is_base_of_v<KernelTmaWarpSpecializedCooperative, typename DispatchPolicy::Schedule> ||
|
||||
cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, typename DispatchPolicy::Schedule>,
|
||||
"Unsupported kernel schedule by this collective mainloop dispatch policy.");
|
||||
|
||||
using SmemCopyAtomA = Copy_Atom<decltype(detail::sm120_rr_smem_copy_selector_A<ElementA, ElementB, UseF8f6f4>()), SmemAllocTypeA>;
|
||||
using SmemCopyAtomB = Copy_Atom<decltype(detail::sm120_rr_smem_copy_selector_B<ElementA, ElementB, UseF8f6f4>()), SmemAllocTypeB>;
|
||||
|
||||
@@ -502,6 +502,9 @@ check_input_datatypes() {
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelScheduleBlockScaledGemmSm120>)
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelTmaWarpSpecializedPingpong>)
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelTmaWarpSpecializedCooperative>)
|
||||
// SM120 BS ptr_array
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelPtrArrayTmaWarpSpecializedPingpong>)
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelPtrArrayTmaWarpSpecializedCooperative>)
|
||||
// SM120 BSSP
|
||||
|| (cute::is_same_v<BuilderScheduleTag, KernelScheduleBlockScaledSparseGemmSm120>)
|
||||
);
|
||||
|
||||
@@ -389,20 +389,44 @@ is_input_fp8() {
|
||||
|
||||
// We need to handle the tuples in this function since it is used in SFINAE dispatch in the CollectiveBuilder.
|
||||
// At that point, it is not guaranteed that the tuples have been split out into the required parts.
|
||||
template <class MaybeTupleElementA, class LayoutA, class MaybeTupleElementB, class LayoutB>
|
||||
template <class MaybeTupleElementA, class MaybePairLayoutA, class MaybeTupleElementB, class MaybePairLayoutB>
|
||||
constexpr bool
|
||||
is_use_rmem_A() {
|
||||
|
||||
using ElementA = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementA>;
|
||||
using ElementB = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementB>;
|
||||
// Handle the case we get a pair of layouts. We expect one of them to be an actual cute layout
|
||||
if constexpr (cute::is_tuple_v<MaybePairLayoutA> and
|
||||
cute::is_tuple_v<MaybePairLayoutB>) {
|
||||
if constexpr ((cute::is_layout<cute::remove_pointer_t<cute::tuple_element_t<0, MaybePairLayoutA>>>::value or
|
||||
cute::is_layout<cute::remove_pointer_t<cute::tuple_element_t<1, MaybePairLayoutA>>>::value) and
|
||||
(cute::is_layout<cute::remove_pointer_t<cute::tuple_element_t<0, MaybePairLayoutB>>>::value or
|
||||
cute::is_layout<cute::remove_pointer_t<cute::tuple_element_t<1, MaybePairLayoutB>>>::value)) {
|
||||
return is_use_rmem_A<MaybeTupleElementA, cute::tuple_element_t<0, MaybePairLayoutA>,
|
||||
MaybeTupleElementB, cute::tuple_element_t<0, MaybePairLayoutB>>();
|
||||
} else {
|
||||
using ElementA = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementA>;
|
||||
using ElementB = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementB>;
|
||||
|
||||
constexpr bool IsABDifferentWidth = cute::sizeof_bits_v<ElementA> != cute::sizeof_bits_v<ElementB>;
|
||||
constexpr bool HasScales = cute::is_tuple<MaybeTupleElementA>::value ^ cute::is_tuple<MaybeTupleElementB>::value;
|
||||
constexpr bool IsInputSizeTwoBytes = is_input_size_two_bytes<ElementA, ElementB>();
|
||||
constexpr bool IsLayoutAkBk = cutlass::gemm::detail::is_k_major_A<LayoutA>() &&
|
||||
cutlass::gemm::detail::is_k_major_B<LayoutB>();
|
||||
constexpr bool IsUseRmemA = (!IsInputSizeTwoBytes && !IsLayoutAkBk) || IsABDifferentWidth || HasScales;
|
||||
return IsUseRmemA;
|
||||
constexpr bool IsABDifferentWidth = cute::sizeof_bits_v<ElementA> != cute::sizeof_bits_v<ElementB>;
|
||||
constexpr bool HasScales = cute::is_tuple<MaybeTupleElementA>::value ^ cute::is_tuple<MaybeTupleElementB>::value;
|
||||
constexpr bool IsInputSizeTwoBytes = is_input_size_two_bytes<ElementA, ElementB>();
|
||||
constexpr bool IsLayoutAkBk = cutlass::gemm::detail::is_k_major_A<MaybePairLayoutA>() &&
|
||||
cutlass::gemm::detail::is_k_major_B<MaybePairLayoutB>();
|
||||
constexpr bool IsUseRmemA = (!IsInputSizeTwoBytes && !IsLayoutAkBk) || IsABDifferentWidth || HasScales;
|
||||
return IsUseRmemA;
|
||||
}
|
||||
}
|
||||
else {
|
||||
using ElementA = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementA>;
|
||||
using ElementB = detail::deduce_mixed_width_dtype_t<0, MaybeTupleElementB>;
|
||||
|
||||
constexpr bool IsABDifferentWidth = cute::sizeof_bits_v<ElementA> != cute::sizeof_bits_v<ElementB>;
|
||||
constexpr bool HasScales = cute::is_tuple<MaybeTupleElementA>::value ^ cute::is_tuple<MaybeTupleElementB>::value;
|
||||
constexpr bool IsInputSizeTwoBytes = is_input_size_two_bytes<ElementA, ElementB>();
|
||||
constexpr bool IsLayoutAkBk = cutlass::gemm::detail::is_k_major_A<MaybePairLayoutA>() &&
|
||||
cutlass::gemm::detail::is_k_major_B<MaybePairLayoutB>();
|
||||
constexpr bool IsUseRmemA = (!IsInputSizeTwoBytes && !IsLayoutAkBk) || IsABDifferentWidth || HasScales;
|
||||
return IsUseRmemA;
|
||||
}
|
||||
}
|
||||
|
||||
template <class ElementA, int AlignmentA, class ElementB, int AlignmentB, int RequiredAlignment>
|
||||
|
||||
@@ -1037,10 +1037,10 @@ static constexpr bool IsMixedWidthInput = IsDifferentWidth || (IsDifferentWidth
|
||||
// GMMA_TMA_WS_SS (BlockScaled Builders)
|
||||
template <
|
||||
class ElementA,
|
||||
class GmemLayoutATag,
|
||||
class GmemLayoutPairA,
|
||||
int AlignmentA,
|
||||
class ElementB,
|
||||
class GmemLayoutBTag,
|
||||
class GmemLayoutPairB,
|
||||
int AlignmentB,
|
||||
class ElementAccumulator,
|
||||
class TileShape_MNK,
|
||||
@@ -1052,10 +1052,10 @@ struct CollectiveBuilder<
|
||||
arch::Sm90,
|
||||
arch::OpClassTensorOp,
|
||||
ElementA,
|
||||
GmemLayoutATag,
|
||||
GmemLayoutPairA,
|
||||
AlignmentA,
|
||||
ElementB,
|
||||
GmemLayoutBTag,
|
||||
GmemLayoutPairB,
|
||||
AlignmentB,
|
||||
ElementAccumulator,
|
||||
TileShape_MNK,
|
||||
@@ -1063,14 +1063,27 @@ struct CollectiveBuilder<
|
||||
StageCountType,
|
||||
KernelScheduleType,
|
||||
cute::enable_if_t<
|
||||
cute::is_same_v<decltype(KernelScheduleType::ScaleGranularityM), decltype(KernelScheduleType::ScaleGranularityN)> and
|
||||
not detail::is_use_rmem_A<ElementA, GmemLayoutATag, ElementB, GmemLayoutBTag>()
|
||||
(cute::is_same_v<KernelScheduleType, KernelTmaWarpSpecializedCooperativeFP8BlockScaledAccum> or
|
||||
cute::is_same_v<KernelScheduleType, KernelPtrArrayTmaWarpSpecializedCooperativeFP8BlockScaledAccum> or
|
||||
cute::is_same_v<KernelScheduleType, KernelPtrArrayTmaWarpSpecializedPingpongFP8BlockScaledAccum>) and
|
||||
not detail::is_use_rmem_A<ElementA, GmemLayoutPairA, ElementB, GmemLayoutPairB>()
|
||||
>
|
||||
> {
|
||||
using GmemLayoutATag = cute::remove_cvref_t<decltype(get<0>(GmemLayoutPairA{}))>;
|
||||
using GmemLayoutSFATag = cute::remove_cvref_t<decltype(get<1>(GmemLayoutPairA{}))>;
|
||||
using GmemLayoutBTag = cute::remove_cvref_t<decltype(get<0>(GmemLayoutPairB{}))>;
|
||||
using GmemLayoutSFBTag = cute::remove_cvref_t<decltype(get<1>(GmemLayoutPairB{}))>;
|
||||
|
||||
static constexpr auto ScaleGranularityM_ = KernelScheduleType::ScaleGranularityM;
|
||||
static constexpr auto ScaleGranularityN_ = KernelScheduleType::ScaleGranularityN;
|
||||
static constexpr auto ScalePromotionInterval_ = KernelScheduleType::ScalePromotionInterval;
|
||||
static_assert(cute::depth(cute::remove_pointer_t<GmemLayoutSFATag>{}) == 2 and
|
||||
cute::depth(cute::remove_pointer_t<GmemLayoutSFBTag>{}) == 2,
|
||||
"Expect SFA and SFB layout to be depth of two with shape ((SFVecMN, restMN),(SFVecK, restK), L)");
|
||||
static_assert(size<1,0>(cute::remove_pointer_t<GmemLayoutSFATag>{}) ==
|
||||
size<1,0>(cute::remove_pointer_t<GmemLayoutSFBTag>{}),
|
||||
"SFA and SFB must have equivalent SF vector sizes along K");
|
||||
|
||||
static constexpr auto ScaleGranularityM = size<0,0>(cute::remove_pointer_t<GmemLayoutSFATag>{});
|
||||
static constexpr auto ScaleGranularityN = size<0,0>(cute::remove_pointer_t<GmemLayoutSFBTag>{});
|
||||
static constexpr auto ScaleGranularityK = size<1,0>(cute::remove_pointer_t<GmemLayoutSFATag>{});
|
||||
|
||||
static_assert(is_static<TileShape_MNK>::value);
|
||||
static_assert(is_static<ClusterShape_MNK>::value);
|
||||
@@ -1113,10 +1126,10 @@ struct CollectiveBuilder<
|
||||
GmmaMajorB, ElementBMma, decltype(cute::get<1>(TileShape_MNK{})), decltype(cute::get<2>(TileShape_MNK{}))>());
|
||||
|
||||
static constexpr size_t TensorMapStorage = IsArrayOfPointersGemm ? sizeof(cute::TmaDescriptor) * 2 /* for A and B */ : 0;
|
||||
static constexpr int KernelSmemCarveout = static_cast<int>(TensorMapStorage);
|
||||
// Reserve 128B for 8 stages of tile scheduling
|
||||
static constexpr size_t TileSchedulerCarveout = IsArrayOfPointersGemm ? 128 : 0;
|
||||
static constexpr int KernelSmemCarveout = static_cast<int>(TensorMapStorage) + static_cast<int>(TileSchedulerCarveout);
|
||||
|
||||
static constexpr int ScaleGranularityM = ScaleGranularityM_ == 0 ? size<0>(TileShape_MNK{}) : ScaleGranularityM_;
|
||||
static constexpr int ScaleGranularityN = ScaleGranularityN_ == 0 ? size<1>(TileShape_MNK{}) : ScaleGranularityN_;
|
||||
static constexpr int ScaleMsPerTile = size<0>(TileShape_MNK{}) / ScaleGranularityM;
|
||||
static constexpr int ScaleNsPerTile = size<1>(TileShape_MNK{}) / ScaleGranularityN;
|
||||
static_assert((size<0>(TileShape_MNK{}) % ScaleGranularityM) == 0, "FP8 scaling granularity must evenly divide tile shape along M.");
|
||||
@@ -1125,8 +1138,8 @@ struct CollectiveBuilder<
|
||||
static constexpr int PipelineStages = detail::compute_stage_count_with_blockwise_scale<detail::sm90_smem_capacity_bytes - KernelSmemCarveout,
|
||||
ElementAMma, ElementBMma, ElementBlockScale, TileShape_MNK, ScaleMsPerTile, ScaleNsPerTile>(StageCountType{});
|
||||
using DispatchPolicy = cute::conditional_t<IsArrayOfPointersGemm,
|
||||
MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<PipelineStages, ClusterShape_MNK, KernelScheduleType, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>,
|
||||
MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<PipelineStages, ClusterShape_MNK, KernelScheduleType, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>>;
|
||||
MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<PipelineStages, ClusterShape_MNK, KernelScheduleType>,
|
||||
MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<PipelineStages, ClusterShape_MNK, KernelScheduleType>>;
|
||||
|
||||
using SmemCopyAtomA = void;
|
||||
using SmemCopyAtomB = void;
|
||||
@@ -1135,9 +1148,9 @@ struct CollectiveBuilder<
|
||||
DispatchPolicy,
|
||||
TileShape_MNK,
|
||||
ElementA,
|
||||
TagToStrideA_t<GmemLayoutATag>,
|
||||
cute::tuple<TagToStrideA_t<GmemLayoutATag>, TagToStrideA_t<GmemLayoutSFATag>>,
|
||||
ElementB,
|
||||
TagToStrideB_t<GmemLayoutBTag>,
|
||||
cute::tuple<TagToStrideB_t<GmemLayoutBTag>, TagToStrideB_t<GmemLayoutSFBTag>>,
|
||||
TiledMma,
|
||||
GmemTiledCopyA,
|
||||
SmemLayoutAtomA,
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
#include "cutlass/gemm/collective/sm100_mma_array_warpspecialized_blockwise_scaling.hpp"
|
||||
#include "cutlass/gemm/collective/sm120_mma_tma.hpp"
|
||||
#include "cutlass/gemm/collective/sm120_blockscaled_mma_tma.hpp"
|
||||
#include "cutlass/gemm/collective/sm120_blockscaled_mma_array_tma.hpp"
|
||||
#include "cutlass/gemm/collective/sm120_sparse_mma_tma.hpp"
|
||||
#include "cutlass/gemm/collective/sm120_blockscaled_sparse_mma_tma.hpp"
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
+11
-4
@@ -171,6 +171,7 @@ struct CollectiveMma<
|
||||
ScaleGranularityK,
|
||||
size<0,1>(InternalLayoutSFA{}.stride()) == 1 ? UMMA::Major::MN : UMMA::Major::K,
|
||||
size<0,1>(InternalLayoutSFB{}.stride()) == 1 ? UMMA::Major::MN : UMMA::Major::K>;
|
||||
|
||||
|
||||
using SmemLayoutAtomSFA = decltype(ScaleConfig::smem_atom_layoutSFA(CtaShape_MNK{}));
|
||||
using SmemLayoutAtomSFB = decltype(ScaleConfig::smem_atom_layoutSFB(CtaShape_MNK{}));
|
||||
@@ -189,6 +190,9 @@ struct CollectiveMma<
|
||||
using TransformB = TransformB_;
|
||||
using ArchTag = typename DispatchPolicy::ArchTag;
|
||||
|
||||
static constexpr int AlignmentSFA = GmemTiledCopySFA::AtomNumVal::value * sizeof(typename GmemTiledCopySFA::ValType) / sizeof(ElementAccumulator);
|
||||
static constexpr int AlignmentSFB = GmemTiledCopySFB::AtomNumVal::value * sizeof(typename GmemTiledCopySFB::ValType) / sizeof(ElementAccumulator);
|
||||
|
||||
using MainloopABPipeline = cutlass::PipelineTmaUmmaAsync<
|
||||
DispatchPolicy::Stages,
|
||||
ClusterShape,
|
||||
@@ -510,6 +514,7 @@ struct CollectiveMma<
|
||||
constexpr int min_tma_aligned_elements_B = tma_alignment_bits_B / cute::sizeof_bits<ElementB>::value;
|
||||
|
||||
bool implementable = true;
|
||||
bool implementable_sf = true;
|
||||
if (problem_shapes.is_host_problem_shape_available()) {
|
||||
// Check alignment for all problem sizes
|
||||
for (int i = 0; i < problem_shapes.groups(); i++) {
|
||||
@@ -517,19 +522,21 @@ struct CollectiveMma<
|
||||
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), InternalStrideA{});
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_B>(cute::make_shape(N,K,L), InternalStrideB{});
|
||||
bool implementable_sf = cutlass::detail::check_alignment<sizeof(typename GmemTiledCopySFA::ValType) / sizeof(ElementAccumulator)>(InternalLayoutSFA{});
|
||||
implementable_sf = implementable_sf && cutlass::detail::check_alignment<sizeof(typename GmemTiledCopySFB::ValType) / sizeof(ElementAccumulator)>(InternalLayoutSFB{});
|
||||
|
||||
implementable_sf = implementable_sf && cutlass::detail::check_alignment<AlignmentSFA>(ScaleConfig::tile_atom_to_shape_SFA(problem_shape_MNKL));
|
||||
implementable_sf = implementable_sf && cutlass::detail::check_alignment<AlignmentSFB>(ScaleConfig::tile_atom_to_shape_SFB(problem_shape_MNKL));
|
||||
if (!implementable_sf) {
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem Size doesn't meet the minimum alignment requirements for Scale Factors.\n");
|
||||
}
|
||||
implementable = implementable && implementable_sf;
|
||||
}
|
||||
}
|
||||
else {
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Ignoring check to can implement because host problem shape is not available.\n");
|
||||
}
|
||||
|
||||
if (!implementable) {
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem Size doesn't meet the minimum alignment requirements for TMA.\n");
|
||||
}
|
||||
implementable = implementable && implementable_sf;
|
||||
return implementable;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/kernel_hardware_info.hpp"
|
||||
#include "cutlass/detail/sm100_tmem_helper.hpp"
|
||||
#include "cutlass/detail/sm100_blockwise_scale_layout.hpp"
|
||||
#include "cutlass/detail/blockwise_scale_layout.hpp"
|
||||
|
||||
#include "cute/algorithm/functional.hpp"
|
||||
#include "cute/arch/cluster_sm90.hpp"
|
||||
@@ -201,6 +201,9 @@ struct CollectiveMma<
|
||||
AtomThrShapeMNK>;
|
||||
using AccumulatorPipelineState = typename AccumulatorPipeline::PipelineState;
|
||||
|
||||
static constexpr int AlignmentSFA = GmemTiledCopySFA::AtomNumVal::value * sizeof(typename GmemTiledCopySFA::ValType) / sizeof(ElementAccumulator);
|
||||
static constexpr int AlignmentSFB = GmemTiledCopySFB::AtomNumVal::value * sizeof(typename GmemTiledCopySFB::ValType) / sizeof(ElementAccumulator);
|
||||
|
||||
// Two arrivals per thread in the warp (1 arrival and 1 arrival through cp.async.mbarrier)
|
||||
static constexpr int NumMainloopSFProducerThreadEvents = 64;
|
||||
|
||||
@@ -563,8 +566,8 @@ struct CollectiveMma<
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem Size doesn't meet the minimum alignment requirements for TMA.\n");
|
||||
}
|
||||
|
||||
bool implementable_sf = cutlass::detail::check_alignment<sizeof(typename GmemTiledCopySFA::ValType) / sizeof(ElementAccumulator)>(args.layout_SFA);
|
||||
implementable_sf = implementable_sf && cutlass::detail::check_alignment<sizeof(typename GmemTiledCopySFB::ValType) / sizeof(ElementAccumulator)>(args.layout_SFB);
|
||||
bool implementable_sf = cutlass::detail::check_alignment<AlignmentSFA>(args.layout_SFA);
|
||||
implementable_sf = implementable_sf && cutlass::detail::check_alignment<AlignmentSFB>(args.layout_SFB);
|
||||
|
||||
if (!implementable_sf) {
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem Size doesn't meet the minimum alignment requirements for Scale Factors.\n");
|
||||
|
||||
@@ -0,0 +1,824 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2025 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 <cuda_bf16.h>
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/dispatch_policy.hpp"
|
||||
#include "cutlass/pipeline/pipeline.hpp"
|
||||
#include "cutlass/numeric_conversion.h"
|
||||
#include "cutlass/detail/sm100_tmem_helper.hpp"
|
||||
#include "cutlass/detail/cluster.hpp"
|
||||
|
||||
#include "cute/algorithm/functional.hpp"
|
||||
#include "cute/arch/cluster_sm90.hpp"
|
||||
#include "cute/atom/mma_atom.hpp"
|
||||
#include "cute/atom/copy_atom.hpp"
|
||||
#include "cute/algorithm/gemm.hpp"
|
||||
#include "cute/tensor_predicate.hpp"
|
||||
#include "cute/arch/mma_sm100.hpp"
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/kernel_hardware_info.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
using namespace cute;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// WarpSpecialized Mainloop for Mixed Input Kernels
|
||||
template <
|
||||
int Load2TransformPipelineStageCount_,
|
||||
int Transform2MmaPipelineStageCount_,
|
||||
int SchedulerPipelineStageCount_,
|
||||
int AccumulatorPipelineStageCount_,
|
||||
class ClusterShape,
|
||||
class TileShape_,
|
||||
class ElementA_,
|
||||
class StrideA_,
|
||||
class ElementB_,
|
||||
class StrideB_,
|
||||
class TiledMma_,
|
||||
class GmemTiledCopyA_,
|
||||
class SmemLayoutAtomsA_,
|
||||
class CopyAtomsA_,
|
||||
class TransformA_,
|
||||
class GmemTiledCopyB_,
|
||||
class SmemLayoutAtomsB_,
|
||||
class CopyAtomsB_,
|
||||
class TransformB_>
|
||||
struct CollectiveMma<
|
||||
MainloopSm100TmaUmmaWarpSpecializedMixedInput<
|
||||
Load2TransformPipelineStageCount_,
|
||||
Transform2MmaPipelineStageCount_,
|
||||
SchedulerPipelineStageCount_,
|
||||
AccumulatorPipelineStageCount_,
|
||||
ClusterShape>,
|
||||
TileShape_,
|
||||
ElementA_,
|
||||
StrideA_,
|
||||
ElementB_,
|
||||
StrideB_,
|
||||
TiledMma_,
|
||||
GmemTiledCopyA_,
|
||||
SmemLayoutAtomsA_,
|
||||
CopyAtomsA_,
|
||||
TransformA_,
|
||||
GmemTiledCopyB_,
|
||||
SmemLayoutAtomsB_,
|
||||
CopyAtomsB_,
|
||||
TransformB_>
|
||||
{
|
||||
//
|
||||
// Type Aliases
|
||||
//
|
||||
|
||||
// Determine MMA type: MMA_1SM vs MMA_2SM
|
||||
using AtomThrShapeMNK = Shape<decltype(shape<0>(typename TiledMma_::ThrLayoutVMNK{})), _1, _1>;
|
||||
using DispatchPolicy = MainloopSm100TmaUmmaWarpSpecializedMixedInput<
|
||||
Load2TransformPipelineStageCount_,
|
||||
Transform2MmaPipelineStageCount_,
|
||||
SchedulerPipelineStageCount_,
|
||||
AccumulatorPipelineStageCount_,
|
||||
ClusterShape>;
|
||||
using TileShape = TileShape_;
|
||||
using TiledMma = TiledMma_;
|
||||
static constexpr bool IsDynamicCluster = not cute::is_static_v<ClusterShape>;
|
||||
using CtaShape_MNK = decltype(shape_div(TileShape{}, AtomThrShapeMNK{}));
|
||||
|
||||
// Define A and B block shapes for reduced size TMA_LOADs
|
||||
using CtaShapeA_MK = decltype(partition_shape_A(TiledMma{}, make_shape(size<0>(TileShape{}), size<2>(TileShape{}))));
|
||||
using CtaShapeB_NK = decltype(partition_shape_B(TiledMma{}, make_shape(size<1>(TileShape{}), size<2>(TileShape{}))));
|
||||
|
||||
using ElementA = ElementA_;
|
||||
using StrideA = StrideA_;
|
||||
using ElementAMma = typename TiledMma::ValTypeA;
|
||||
|
||||
static constexpr int IsSubbyteA = cute::sizeof_bits_v<ElementA> < 8;
|
||||
using TmaElementA = cute::conditional_t<IsSubbyteA, uint8_t, ElementA>;
|
||||
|
||||
using ElementB = ElementB_;
|
||||
using StrideB = StrideB_;
|
||||
using ElementBMma = typename TiledMma::ValTypeB;
|
||||
|
||||
using StrideScale = cute::Stride<cute::Int<1>, int64_t, int64_t>;
|
||||
using NonVoidStrideScale = cute::conditional_t<
|
||||
cute::is_void_v<StrideScale>, cute::Stride<_1, int64_t, int64_t>, StrideScale>;
|
||||
|
||||
using ElementAccumulator = typename TiledMma::ValTypeC;
|
||||
using GmemTiledCopyA = GmemTiledCopyA_;
|
||||
using GmemTiledCopyB = GmemTiledCopyB_;
|
||||
using SmemLayoutAtomsA = SmemLayoutAtomsA_;
|
||||
using SmemLayoutAtomsB = SmemLayoutAtomsB_;
|
||||
using CopyAtomsA = CopyAtomsA_;
|
||||
using CopyAtomsB = CopyAtomsB_;
|
||||
using TransformA = TransformA_;
|
||||
using TransformB = TransformB_;
|
||||
using ArchTag = typename DispatchPolicy::ArchTag;
|
||||
|
||||
static_assert(sizeof(ElementA) < 2, "Matrix to be scaled should be provided in A otherwise input is not supported");
|
||||
static_assert(cute::is_same_v<ElementAMma, cutlass::bfloat16_t> || cute::is_same_v<ElementAMma, cutlass::half_t> || cute::is_same_v<ElementAMma, cutlass::float_e4m3_t>, "Compute type A should be cutlass::bfloat16_t or cutlass::half_t or cutlass::float_e4m3_t");
|
||||
|
||||
using Load2TransformPipeline = cutlass::PipelineTmaTransformAsync<
|
||||
DispatchPolicy::Load2TransformPipelineStageCount,
|
||||
AtomThrShapeMNK>;
|
||||
using Load2TransformPipelineState = typename Load2TransformPipeline::PipelineState;
|
||||
|
||||
using Transform2MmaPipeline = cutlass::PipelineUmmaConsumerAsync<
|
||||
DispatchPolicy::Transform2MmaPipelineStageCount,
|
||||
AtomThrShapeMNK>;
|
||||
using Transform2MmaPipelineState = typename Transform2MmaPipeline::PipelineState;
|
||||
|
||||
using Mma2AccumPipeline = cutlass::PipelineUmmaAsync<
|
||||
DispatchPolicy::Schedule::AccumulatorPipelineStageCount,
|
||||
AtomThrShapeMNK>;
|
||||
using Mma2AccumPipelineState = typename Mma2AccumPipeline::PipelineState;
|
||||
|
||||
// Thread Counts
|
||||
static constexpr uint32_t NumAccumThreads = 128; //Maintains compatibility with input_transform kernel
|
||||
static constexpr uint32_t NumTransformationThreads = 128;
|
||||
|
||||
// Get the Algorithm parameters
|
||||
constexpr static int AccumulatorPipelineStageCount = DispatchPolicy::Schedule::AccumulatorPipelineStageCount;
|
||||
constexpr static int StagesPerTile = size<2>(CtaShapeA_MK{});
|
||||
|
||||
using SmemLayoutAtomA = typename SmemLayoutAtomsA::InputLayoutAtom;
|
||||
using SmemLayoutAtomACompute = typename SmemLayoutAtomsA::ComputeLayoutAtom;
|
||||
using SmemLayoutAtomB = typename SmemLayoutAtomsB::InputLayoutAtom;
|
||||
using SmemLayoutAtomBCompute = typename SmemLayoutAtomsB::ComputeLayoutAtom;
|
||||
|
||||
using InputCopyAtomA = typename CopyAtomsA::InputCopyAtom;
|
||||
using ComputeCopyAtomA = typename CopyAtomsA::ComputeCopyAtom;
|
||||
using InputCopyAtomB = typename CopyAtomsB::InputCopyAtom;
|
||||
using ComputeCopyAtomB = typename CopyAtomsB::ComputeCopyAtom;
|
||||
|
||||
static_assert(rank(SmemLayoutAtomA{}) == 2, "SmemLayoutAtom must be rank 2 (M/N, K)");
|
||||
static_assert(((size<0,0>(CtaShapeA_MK{}) * size<1>(CtaShapeA_MK{})) % size<0>(SmemLayoutAtomACompute{})) == 0, "SmemLayoutAtomCompute must evenly divide tile shape.");
|
||||
static_assert(((size<0,1>(CtaShapeA_MK{}) * size<2>(CtaShapeA_MK{})) % size<1>(SmemLayoutAtomACompute{})) == 0, "SmemLayoutAtomCompute must evenly divide tile shape.");
|
||||
|
||||
static_assert(rank(SmemLayoutAtomB{}) == 2, "SmemLayoutAtom must be rank 2 (M/N, K)");
|
||||
static_assert(((size<0,0>(CtaShapeB_NK{}) * size<1>(CtaShapeB_NK{})) % size<0>(SmemLayoutAtomBCompute{})) == 0, "SmemLayoutAtomCompute must evenly divide tile shape.");
|
||||
static_assert(((size<0,1>(CtaShapeB_NK{}) * size<2>(CtaShapeB_NK{})) % size<1>(SmemLayoutAtomBCompute{})) == 0, "SmemLayoutAtomCompute must evenly divide tile shape.");
|
||||
|
||||
// Tile along K mode first before tiling over MN. PIPE mode last as usual.
|
||||
// This maximizes TMA boxes due to better smem-K vectorization, reducing total issued TMAs.
|
||||
using SmemLayoutA = decltype(UMMA::tile_to_mma_shape(
|
||||
SmemLayoutAtomA{},
|
||||
append(CtaShapeA_MK{}, Int<DispatchPolicy::Load2TransformPipelineStageCount>{}),
|
||||
(cute::conditional_t<cutlass::gemm::detail::is_mn_major<StrideA>(), Step<_2,_1,_3>, Step<_1,_2,_3>>{})));
|
||||
|
||||
using SmemLayoutACompute = decltype(UMMA::tile_to_mma_shape(
|
||||
SmemLayoutAtomACompute{},
|
||||
append(CtaShapeA_MK{}, Int<DispatchPolicy::Load2TransformPipelineStageCount>{}),
|
||||
(cute::conditional_t<cutlass::gemm::detail::is_mn_major<StrideA>(), Step<_2,_1,_3>, Step<_1,_2,_3>>{})));
|
||||
|
||||
using SmemLayoutB = decltype(UMMA::tile_to_mma_shape(
|
||||
SmemLayoutAtomB{},
|
||||
append(CtaShapeB_NK{}, Int<DispatchPolicy::Load2TransformPipelineStageCount>{}),
|
||||
(cute::conditional_t<cutlass::gemm::detail::is_mn_major<StrideB>(), Step<_2,_1,_3>, Step<_1,_2,_3>>{})));
|
||||
|
||||
static_assert(DispatchPolicy::Load2TransformPipelineStageCount >= 2 && DispatchPolicy::Load2TransformPipelineStageCount >= 2,
|
||||
"Specialization requires Stages set to value 2 or more.");
|
||||
static_assert((cute::is_base_of<cute::UMMA::DescriptorIterator, typename TiledMma::FrgTypeA>::value ||
|
||||
cute::is_base_of<cute::UMMA::tmem_frg_base, typename TiledMma::FrgTypeA>::value ) &&
|
||||
cute::is_base_of<cute::UMMA::DescriptorIterator, typename TiledMma::FrgTypeB>::value,
|
||||
"MMA atom must A operand from SMEM or TMEM and B operand from SMEM for this mainloop.");
|
||||
static_assert((cute::is_same_v<GmemTiledCopyA, SM90_TMA_LOAD> || cute::is_same_v<GmemTiledCopyA, SM90_TMA_LOAD_MULTICAST>),
|
||||
"GmemTiledCopyA - invalid TMA copy atom specified.");
|
||||
static_assert((cute::is_same_v<GmemTiledCopyB, SM90_TMA_LOAD> || cute::is_same_v<GmemTiledCopyB, SM90_TMA_LOAD_MULTICAST>),
|
||||
"GmemTiledCopyB - invalid TMA copy atom specified.");
|
||||
|
||||
struct PipelineStorage {
|
||||
using Load2TransformPipelineStorage = typename Load2TransformPipeline::SharedStorage;
|
||||
alignas(16) Load2TransformPipelineStorage load2transform_pipeline;
|
||||
using Transform2MmaPipelineStorage = typename Transform2MmaPipeline::SharedStorage;
|
||||
alignas(16) Transform2MmaPipelineStorage transform2mma_pipeline;
|
||||
using Mma2AccumPipelineStorage = typename Mma2AccumPipeline::SharedStorage;
|
||||
alignas(16) Mma2AccumPipelineStorage mma2accum_pipeline;
|
||||
};
|
||||
|
||||
struct SharedStorage {
|
||||
struct TensorStorage : cute::aligned_struct<128, _0> {
|
||||
|
||||
struct TensorStorageUntransformed {
|
||||
cute::ArrayEngine<ElementA, cute::cosize_v<SmemLayoutA>> smem_A;
|
||||
cute::ArrayEngine<ElementB, cute::cosize_v<SmemLayoutB>> smem_B;
|
||||
};
|
||||
|
||||
struct TensorStorageTransformedAinSmem {
|
||||
alignas(1024) cute::ArrayEngine<ElementAMma, cute::cosize_v<SmemLayoutACompute>> smem_ACompute;
|
||||
alignas(1024) cute::ArrayEngine<ElementBMma, cute::cosize_v<SmemLayoutB>> smem_BCompute;
|
||||
};
|
||||
|
||||
union TensorStorageTransformedAinTmem {
|
||||
alignas(1024) cute::ArrayEngine<ElementAMma, 1> smem_ACompute; // No smem_ACompute
|
||||
alignas(1024) cute::ArrayEngine<ElementBMma, cute::cosize_v<SmemLayoutB>> smem_BCompute;
|
||||
};
|
||||
|
||||
using TensorStorageTransformed = cute::conditional_t<
|
||||
cute::is_base_of<cute::UMMA::DescriptorIterator, typename TiledMma::FrgTypeA>::value,
|
||||
TensorStorageTransformedAinSmem,
|
||||
TensorStorageTransformedAinTmem>;
|
||||
|
||||
TensorStorageUntransformed input;
|
||||
TensorStorageTransformed compute;
|
||||
} tensors;
|
||||
|
||||
PipelineStorage pipeline;
|
||||
};
|
||||
using TensorStorage = typename SharedStorage::TensorStorage;
|
||||
|
||||
// Different from other GEMM kernels, both CTAs should be aware of loads. Both CTAs will work on
|
||||
// loaded input A and B matrices to convert the data type
|
||||
static constexpr uint32_t TmaTransactionBytes =
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutA{}) * size<1>(SmemLayoutA{}) * size<2>(SmemLayoutA{}) * static_cast<uint32_t>(sizeof_bits<ElementA>::value))+
|
||||
cutlass::bits_to_bytes(size<0>(SmemLayoutB{}) * size<1>(SmemLayoutB{}) * size<2>(SmemLayoutB{}) * static_cast<uint32_t>(sizeof_bits<ElementB>::value));
|
||||
|
||||
// Host side kernel arguments
|
||||
struct Arguments {
|
||||
ElementA const* ptr_A{nullptr};
|
||||
StrideA dA{};
|
||||
ElementB const* ptr_B{nullptr};
|
||||
StrideB dB{};
|
||||
};
|
||||
|
||||
// Device side kernel params
|
||||
struct Params {
|
||||
using ClusterLayout_VMNK = decltype(tiled_divide(make_layout(conditional_return<IsDynamicCluster>(make_shape(uint32_t(0), uint32_t(0), Int<1>{}), ClusterShape{})),
|
||||
make_tile(typename TiledMma::AtomThrID{})));
|
||||
|
||||
using TMA_A = decltype(make_tma_atom_A_sm100<TmaElementA>(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(static_cast<ElementA const*>(nullptr), repeat_like(StrideA{}, int32_t(0)), StrideA{}),
|
||||
SmemLayoutA{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
ClusterLayout_VMNK{})
|
||||
);
|
||||
using TMA_B = decltype(make_tma_atom_B_sm100<ElementB>(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(static_cast<ElementB const*>(nullptr), repeat_like(StrideB{}, int32_t(0)), StrideB{}),
|
||||
SmemLayoutB{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
ClusterLayout_VMNK{})
|
||||
);
|
||||
TMA_A tma_load_a;
|
||||
TMA_B tma_load_b;
|
||||
TMA_A tma_load_a_fallback;
|
||||
TMA_B tma_load_b_fallback;
|
||||
dim3 cluster_shape_fallback;
|
||||
};
|
||||
|
||||
CUTLASS_DEVICE
|
||||
CollectiveMma(Params const& params, ClusterShape cluster_shape, uint32_t block_rank_in_cluster)
|
||||
: cluster_shape_(cluster_shape)
|
||||
, block_rank_in_cluster_(block_rank_in_cluster) {
|
||||
if constexpr (IsDynamicCluster) {
|
||||
const bool is_fallback_cluster = (cute::size<0>(cluster_shape_) == params.cluster_shape_fallback.x &&
|
||||
cute::size<1>(cluster_shape_) == params.cluster_shape_fallback.y);
|
||||
observed_tma_load_a_ = is_fallback_cluster ? ¶ms.tma_load_a_fallback : ¶ms.tma_load_a;
|
||||
observed_tma_load_b_ = is_fallback_cluster ? ¶ms.tma_load_b_fallback : ¶ms.tma_load_b;
|
||||
}
|
||||
else {
|
||||
observed_tma_load_a_ = ¶ms.tma_load_a;
|
||||
observed_tma_load_b_ = ¶ms.tma_load_b;
|
||||
}
|
||||
}
|
||||
|
||||
template <class ProblemShape>
|
||||
static constexpr Params
|
||||
to_underlying_arguments(ProblemShape const& problem_shape, Arguments const& args, void* workspace, cutlass::KernelHardwareInfo const& hw_info = cutlass::KernelHardwareInfo{}) {
|
||||
(void) workspace;
|
||||
|
||||
// Optionally append 1s until problem shape is rank-4 (MNKL), in case it is only rank-3 (MNK)
|
||||
auto problem_shape_MNKL = append<4>(problem_shape, 1);
|
||||
auto [M,N,K,L] = problem_shape_MNKL;
|
||||
|
||||
Tensor tensor_a = make_tensor(args.ptr_A, make_layout(make_shape(M,K,L), args.dA));
|
||||
Tensor tensor_b = make_tensor(args.ptr_B, make_layout(make_shape(N,K,L), args.dB));
|
||||
|
||||
auto cluster_shape = cutlass::detail::select_cluster_shape(ClusterShape{}, hw_info.cluster_shape);
|
||||
// Cluster layout for TMA construction
|
||||
auto cluster_layout_vmnk = tiled_divide(make_layout(cluster_shape), make_tile(typename TiledMma::AtomThrID{}));
|
||||
|
||||
auto cluster_shape_fallback = cutlass::detail::select_cluster_shape(ClusterShape{}, hw_info.cluster_shape_fallback);
|
||||
// Cluster layout for TMA construction
|
||||
auto cluster_layout_vmnk_fallback = tiled_divide(make_layout(cluster_shape_fallback), make_tile(typename TiledMma::AtomThrID{}));
|
||||
|
||||
typename Params::TMA_A tma_load_a = make_tma_atom_A_sm100<TmaElementA>(
|
||||
GmemTiledCopyA{},
|
||||
tensor_a,
|
||||
SmemLayoutA{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
cluster_layout_vmnk);
|
||||
|
||||
typename Params::TMA_B tma_load_b = make_tma_atom_B_sm100<ElementB>(
|
||||
GmemTiledCopyB{},
|
||||
tensor_b,
|
||||
SmemLayoutB{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
cluster_layout_vmnk);
|
||||
|
||||
typename Params::TMA_A tma_load_a_fallback = make_tma_atom_A_sm100<TmaElementA>(
|
||||
GmemTiledCopyA{},
|
||||
tensor_a,
|
||||
SmemLayoutA{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
cluster_layout_vmnk_fallback);
|
||||
|
||||
typename Params::TMA_B tma_load_b_fallback = make_tma_atom_B_sm100<ElementB>(
|
||||
GmemTiledCopyB{},
|
||||
tensor_b,
|
||||
SmemLayoutB{}(_,_,_,cute::Int<0>{}),
|
||||
TileShape{},
|
||||
TiledMma{},
|
||||
cluster_layout_vmnk_fallback);
|
||||
|
||||
return {
|
||||
tma_load_a,
|
||||
tma_load_b,
|
||||
tma_load_a_fallback,
|
||||
tma_load_b_fallback,
|
||||
hw_info.cluster_shape_fallback
|
||||
};
|
||||
}
|
||||
|
||||
template<class ProblemShape>
|
||||
static bool
|
||||
can_implement(
|
||||
ProblemShape const& problem_shape,
|
||||
[[maybe_unused]] Arguments const& args) {
|
||||
constexpr int tma_alignment_bits = 128;
|
||||
auto problem_shape_MNKL = append<4>(problem_shape, 1);
|
||||
auto [M,N,K,L] = problem_shape_MNKL;
|
||||
|
||||
bool implementable = true;
|
||||
constexpr int min_tma_aligned_elements_A = tma_alignment_bits / cutlass::sizeof_bits<ElementA>::value;
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_A>(cute::make_shape(M,K,L), StrideA{});
|
||||
constexpr int min_tma_aligned_elements_B = tma_alignment_bits / cutlass::sizeof_bits<ElementB>::value;
|
||||
implementable = implementable && cutlass::detail::check_alignment<min_tma_aligned_elements_B>(cute::make_shape(N,K,L), StrideB{});
|
||||
|
||||
if (!implementable) {
|
||||
CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Problem Size doesn't meet the minimum alignment requirements for TMA.\n");
|
||||
}
|
||||
return implementable;
|
||||
}
|
||||
|
||||
/// Issue Tma Descriptor Prefetch -- ideally from a single thread for best performance
|
||||
CUTLASS_DEVICE static void
|
||||
prefetch_tma_descriptors(Params const& params) {
|
||||
if constexpr (IsDynamicCluster) {
|
||||
dim3 cs = cute::cluster_shape();
|
||||
const bool is_fallback_cluster = (cs.x == params.cluster_shape_fallback.x && cs.y == params.cluster_shape_fallback.y);
|
||||
if (is_fallback_cluster) {
|
||||
cute::prefetch_tma_descriptor(params.tma_load_a_fallback.get_tma_descriptor());
|
||||
cute::prefetch_tma_descriptor(params.tma_load_b_fallback.get_tma_descriptor());
|
||||
}
|
||||
else {
|
||||
cute::prefetch_tma_descriptor(params.tma_load_a.get_tma_descriptor());
|
||||
cute::prefetch_tma_descriptor(params.tma_load_b.get_tma_descriptor());
|
||||
}
|
||||
}
|
||||
else {
|
||||
cute::prefetch_tma_descriptor(params.tma_load_a.get_tma_descriptor());
|
||||
cute::prefetch_tma_descriptor(params.tma_load_b.get_tma_descriptor());
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct A Single Stage's Accumulator Shape
|
||||
CUTLASS_DEVICE auto
|
||||
partition_accumulator_shape() {
|
||||
auto acc_shape = partition_shape_C(TiledMma{}, take<0,2>(TileShape{})); // ((MMA_TILE_M,MMA_TILE_N),MMA_M,MMA_N)
|
||||
|
||||
return acc_shape;
|
||||
}
|
||||
|
||||
/// Produce the inputs to the transform threads by loading inputs from gmem -> smem
|
||||
template <
|
||||
class GTensorA, class GTensorB,
|
||||
class GTensorPartitionedA, class GTensorPartitionedB,
|
||||
class STensorA, class STensorB,
|
||||
class TileCoordMNKL,
|
||||
class KTileIterator
|
||||
>
|
||||
CUTLASS_DEVICE auto
|
||||
load(
|
||||
Params const& params,
|
||||
Load2TransformPipeline pipeline,
|
||||
Load2TransformPipelineState load2xform_pipeline_state,
|
||||
cute::tuple<GTensorA, GTensorB,
|
||||
GTensorPartitionedA, GTensorPartitionedB,
|
||||
STensorA, STensorB,
|
||||
uint16_t, uint16_t> const& load_inputs,
|
||||
TileCoordMNKL const& cta_coord_mnkl,
|
||||
KTileIterator k_tile_iter, int k_tile_count) {
|
||||
|
||||
auto [unused_gA, unused_gB,
|
||||
tAgA_mkl, tBgB_nkl, tAsA, tBsB,
|
||||
mcast_mask_a, mcast_mask_b] = load_inputs;
|
||||
|
||||
// slice out the work coord from tiled tensors
|
||||
Tensor tAgA = tAgA_mkl(_, get<0>(cta_coord_mnkl) / size(typename TiledMma::AtomThrID{}), _, get<3>(cta_coord_mnkl));
|
||||
Tensor tBgB = tBgB_nkl(_, get<1>(cta_coord_mnkl), _, get<3>(cta_coord_mnkl));
|
||||
|
||||
uint32_t skip_wait = (k_tile_count <= 0);
|
||||
auto pipeline_flag = pipeline.producer_try_acquire(load2xform_pipeline_state, skip_wait);
|
||||
|
||||
// Issue the Mainloop loads
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
// LOCK mainloop_load2xform_pipeline_state for _writing_
|
||||
pipeline.producer_acquire(load2xform_pipeline_state, pipeline_flag);
|
||||
int write_stage = load2xform_pipeline_state.index();
|
||||
|
||||
using BarrierType = typename Load2TransformPipeline::ProducerBarrierType;
|
||||
BarrierType* tma_barrier = pipeline.producer_get_barrier(load2xform_pipeline_state);
|
||||
|
||||
// Advance mainloop_pipe
|
||||
++load2xform_pipeline_state;
|
||||
|
||||
skip_wait = (k_tile_count <= 1);
|
||||
pipeline_flag = pipeline.producer_try_acquire(load2xform_pipeline_state, skip_wait);
|
||||
|
||||
copy(observed_tma_load_a_->with(*tma_barrier, mcast_mask_a), tAgA(_,*k_tile_iter), tAsA(_,write_stage));
|
||||
copy(observed_tma_load_b_->with(*tma_barrier, mcast_mask_b), tBgB(_,*k_tile_iter), tBsB(_,write_stage));
|
||||
|
||||
++k_tile_iter;
|
||||
}
|
||||
|
||||
return cute::make_tuple(load2xform_pipeline_state, k_tile_iter);
|
||||
|
||||
}
|
||||
|
||||
/// Set up the data needed by this collective for load.
|
||||
/// Returned tuple must contain at least two elements, with the first two elements being:
|
||||
/// gA_mkl - The tiled tensor for input A
|
||||
/// gB_nkl - The tiled tensor for input B
|
||||
// Other inputs needed for load(): partitioned AB tensors for gmem and smem, and mcast masks
|
||||
template <class ProblemShape_MNKL>
|
||||
CUTLASS_DEVICE auto
|
||||
load_init(
|
||||
ProblemShape_MNKL const& problem_shape_MNKL,
|
||||
Params const& params,
|
||||
TensorStorage& shared_storage) const {
|
||||
auto [gA_mkl, gB_nkl] = tile_input_tensors(params, problem_shape_MNKL);
|
||||
|
||||
ThrMMA cta_mma = TiledMma{}.get_slice(blockIdx.x % size(typename TiledMma::AtomThrID{}));
|
||||
|
||||
Tensor tCgA_mkl = cta_mma.partition_A(gA_mkl); // (MMA, MMA_M, MMA_K, m, k, l)
|
||||
Tensor tCgB_nkl = cta_mma.partition_B(gB_nkl); // (MMA, MMA_N, MMA_K, n, k, l)
|
||||
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_storage.input.smem_A.begin()), SmemLayoutA{}); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_storage.input.smem_B.begin()), SmemLayoutB{}); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
// Define the CTA-in-cluster Layout and Coord
|
||||
Layout cta_layout_mnk = make_layout(cluster_shape_);
|
||||
Layout cta_layout_vmnk = tiled_divide(cta_layout_mnk, make_tile(typename TiledMma::AtomThrID{}));
|
||||
auto cta_coord_vmnk = cta_layout_vmnk.get_flat_coord(block_rank_in_cluster_);
|
||||
|
||||
// Project the cta_layout for tma_a along the n-modes
|
||||
auto [tAgA_mkl, tAsA] = tma_partition(*observed_tma_load_a_,
|
||||
get<2>(cta_coord_vmnk), make_layout(size<2>(cta_layout_vmnk)),
|
||||
group_modes<0,3>(sA), group_modes<0,3>(tCgA_mkl));
|
||||
|
||||
// Project the cta_layout for tma_b along the m-modes
|
||||
auto [tBgB_nkl, tBsB] = tma_partition(*observed_tma_load_b_,
|
||||
get<1>(cta_coord_vmnk), make_layout(size<1>(cta_layout_vmnk)),
|
||||
group_modes<0,3>(sB), group_modes<0,3>(tCgB_nkl));
|
||||
|
||||
// TMA Multicast Masks
|
||||
uint16_t mcast_mask_a = create_tma_multicast_mask<2>(cta_layout_vmnk, cta_coord_vmnk);
|
||||
uint16_t mcast_mask_b = create_tma_multicast_mask<1>(cta_layout_vmnk, cta_coord_vmnk);
|
||||
|
||||
|
||||
return cute::make_tuple(
|
||||
gA_mkl, gB_nkl, // for scheduler
|
||||
tAgA_mkl, tBgB_nkl, tAsA, tBsB, // for input tensor values
|
||||
mcast_mask_a, mcast_mask_b); // multicast masks
|
||||
}
|
||||
|
||||
template<
|
||||
class KTileIterator, class Accumulator,
|
||||
class GTensorA, class DstCopyA, class SrcTensorA, class DstTensorA,
|
||||
class GTensorB
|
||||
>
|
||||
CUTLASS_DEVICE auto
|
||||
transform(
|
||||
Load2TransformPipeline load2transform_pipeline,
|
||||
Load2TransformPipelineState load2transform_pipeline_consumer_state,
|
||||
Transform2MmaPipeline transform2mma_pipeline,
|
||||
Transform2MmaPipelineState transform2mma_pipeline_producer_state,
|
||||
Accumulator accumulators,
|
||||
cute::tuple<GTensorA, DstCopyA, SrcTensorA, DstTensorA,
|
||||
GTensorB> input_operands,
|
||||
KTileIterator k_tile_iter, int k_tile_count) {
|
||||
|
||||
cutlass::arch::NamedBarrier transform_bar(NumTransformationThreads, cutlass::arch::ReservedNamedBarriers::TransformBarrier);
|
||||
|
||||
// tAsA : (Copy,#Copy),MMA_Rest,MMA_M_Rest,MMA_K_Rest, SmemStages (In SMEM)
|
||||
// tAdA : (Copy,#Copy),MMA_Rest,MMA_M_Rest,MMA_K_Rest, NumComputeMtxs, SmemStages (In SMEM or TMEM)
|
||||
// tBsB : (Copy,#Copy),MMA_Rest,MMA_N_Rest,MMA_K_Rest, SmemStages (In SMEM)
|
||||
// tBsB : (Copy,#Copy),MMA_Rest,MMA_N_Rest,MMA_K_Rest, NumComputeMtxs, SmemStages (In SMEM)
|
||||
auto [unused_tAgA, dst_copy_A, tAsA, tAsACompute,
|
||||
unused_tBgB] = input_operands;
|
||||
|
||||
// Create the tensors in registers
|
||||
auto tArA = make_tensor<ElementA>(tAsA(_,_,_,_,0).shape());
|
||||
auto tArACompute = make_tensor<ElementAMma>(tAsA(_,_,_,_,0).shape());
|
||||
|
||||
auto tArA_x2 = recast<Array<ElementA,2>>(tArA);
|
||||
auto tArACompute_x2 = recast<Array<ElementAMma,2>>(tArACompute);
|
||||
|
||||
|
||||
uint32_t skip_wait = (k_tile_count <= 0);
|
||||
auto load2transform_flag = load2transform_pipeline.consumer_try_wait(load2transform_pipeline_consumer_state, skip_wait);
|
||||
auto transform2mma_flag = transform2mma_pipeline.producer_try_acquire(transform2mma_pipeline_producer_state, skip_wait);
|
||||
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
|
||||
load2transform_pipeline.consumer_wait(load2transform_pipeline_consumer_state, load2transform_flag);
|
||||
transform2mma_pipeline.producer_acquire(transform2mma_pipeline_producer_state, transform2mma_flag);
|
||||
|
||||
int load2transform_consumer_index = load2transform_pipeline_consumer_state.index(); // read stage
|
||||
int transform2mma_producer_index = transform2mma_pipeline_producer_state.index(); //write stage
|
||||
|
||||
auto curr_load2transform_pipeline_consumer_state = load2transform_pipeline_consumer_state;
|
||||
auto curr_transform2mma_pipeline_producer_state = transform2mma_pipeline_producer_state;
|
||||
|
||||
// Copy the input A matrix from SMEM
|
||||
copy(AutoVectorizingCopy{}, tAsA(_,_,_,_,load2transform_consumer_index), tArA);
|
||||
//Transform Input A stored in registers
|
||||
cute::transform(tArA_x2, tArACompute_x2, cutlass::NumericArrayConverter<ElementAMma, ElementA, 2, cutlass::FloatRoundStyle::round_to_nearest_satfinite>::convert);
|
||||
//Transformed A stored in TMEM
|
||||
copy(dst_copy_A, tArACompute, tAsACompute(_,_,_,_,transform2mma_producer_index));
|
||||
|
||||
// Loads from SMEM are done. Signal the mainloop load as early as possible
|
||||
transform_bar.sync();
|
||||
load2transform_pipeline.consumer_release(curr_load2transform_pipeline_consumer_state);
|
||||
|
||||
// fence for SMEM writes
|
||||
cutlass::arch::fence_view_async_shared();
|
||||
if constexpr (is_tmem<decltype(tAsACompute)>::value) {
|
||||
// fence for TMEM writes if A operand is coming from TMEM
|
||||
cutlass::arch::fence_view_async_tmem_store();
|
||||
}
|
||||
|
||||
// Let the MMA know we are done transforming
|
||||
transform2mma_pipeline.producer_commit(curr_transform2mma_pipeline_producer_state);
|
||||
|
||||
// Next pipeline stage
|
||||
++load2transform_pipeline_consumer_state;
|
||||
++transform2mma_pipeline_producer_state;
|
||||
|
||||
skip_wait = (k_tile_count <= 1);
|
||||
// Peek the next pipeline stage's barriers
|
||||
load2transform_flag = load2transform_pipeline.consumer_try_wait(load2transform_pipeline_consumer_state, skip_wait);
|
||||
transform2mma_flag = transform2mma_pipeline.producer_try_acquire(transform2mma_pipeline_producer_state, skip_wait);
|
||||
}
|
||||
return cute::make_tuple(load2transform_pipeline_consumer_state, transform2mma_pipeline_producer_state);
|
||||
}
|
||||
|
||||
template<class ProblemShape_MNKL, class Accumulator>
|
||||
CUTLASS_DEVICE auto
|
||||
transform_init(
|
||||
Params const& params,
|
||||
ProblemShape_MNKL const& problem_shape_MNKL,
|
||||
Accumulator accumulators,
|
||||
TensorStorage& shared_storage) {
|
||||
|
||||
auto [gA_mkl, gB_nkl] = tile_input_tensors(params, problem_shape_MNKL);
|
||||
|
||||
Tensor sA_orig = make_tensor(make_smem_ptr(shared_storage.input.smem_A.begin()), SmemLayoutA{});
|
||||
Tensor sA = as_position_independent_swizzle_tensor(sA_orig); //tCsA
|
||||
Tensor sACompute = make_tensor(make_smem_ptr(shared_storage.compute.smem_ACompute.begin()), SmemLayoutACompute{}); //tCsACompute
|
||||
|
||||
// Map input, compute, and fragment tensors to
|
||||
// Copy strategies and partitioned tensors. These will become the input
|
||||
// operands of the transform function. Depending on MMA atom type, the
|
||||
// operands can reside in SMEM or TMEM
|
||||
auto setup_copy_ops = [&] (
|
||||
auto tensor_input,
|
||||
auto input_copy_atom,
|
||||
auto tensor_compute,
|
||||
auto make_fragment,
|
||||
auto compute_copy_atom) constexpr {
|
||||
|
||||
auto fragment_compute = make_fragment(tensor_compute); //tCrA(Compute)
|
||||
if constexpr (cute::is_tmem<cute::remove_cvref_t<decltype(fragment_compute)>>::value) {
|
||||
// For M=128 with 2CTA MMA atoms, the TMEM tensor for A has a duplicated allocation.
|
||||
// Instead of allocation a 64x16 TMEM tensor, we have a 128x16 allocation
|
||||
// See: TmemAllocMode::Duplicated.
|
||||
Tensor tensor_input2x = [&] () constexpr {
|
||||
if constexpr (decltype(size<0,0>(fragment_compute) == Int<128>{} && size<0,0>(tensor_input) == Int<64>{})::value) {
|
||||
return make_tensor(tensor_input.data(),
|
||||
logical_product(tensor_input.layout(),
|
||||
make_tile(make_tile(Layout<_2,_0>{},_),_,_,_))); // ((128,16),m,k,PIPE)
|
||||
}
|
||||
else {
|
||||
return tensor_input;
|
||||
}
|
||||
}(); //tCsA_2x
|
||||
|
||||
fragment_compute.data() = accumulators.data().get() + cutlass::detail::find_tmem_tensor_col_offset(accumulators); //tCrA.data()
|
||||
auto reg2tmem_tiled_copy = make_tmem_copy(compute_copy_atom, fragment_compute(_,_,0,0));
|
||||
auto thr_reg2tmem_tiled_copy = reg2tmem_tiled_copy.get_slice(threadIdx.x % NumTransformationThreads);
|
||||
auto partitioned_tensor_input = thr_reg2tmem_tiled_copy.partition_S(tensor_input2x);
|
||||
auto partitioned_tensor_compute = thr_reg2tmem_tiled_copy.partition_D(fragment_compute);
|
||||
return cute::make_tuple(reg2tmem_tiled_copy, partitioned_tensor_input, partitioned_tensor_compute);
|
||||
}
|
||||
else {
|
||||
auto tensor_compute_ind_sw = as_position_independent_swizzle_tensor(tensor_compute);
|
||||
auto reg2smem_tiled_copy = make_cotiled_copy(compute_copy_atom, Layout<Shape <_128,_8>, Stride< _8,_1>>{},
|
||||
tensor_compute(_,_,0,0).layout());
|
||||
|
||||
auto thr_reg2smem_tiled_copy = reg2smem_tiled_copy.get_slice(threadIdx.x % NumTransformationThreads);
|
||||
auto partitioned_tensor_input = thr_reg2smem_tiled_copy.partition_S(tensor_input);
|
||||
auto partitioned_tensor_compute = thr_reg2smem_tiled_copy.partition_D(tensor_compute_ind_sw);
|
||||
|
||||
return cute::make_tuple(AutoVectorizingCopy{}, partitioned_tensor_input, partitioned_tensor_compute);
|
||||
}
|
||||
};
|
||||
|
||||
auto [dst_copy_A, tAsA, tAsACompute] =
|
||||
setup_copy_ops(sA, InputCopyAtomA{}, sACompute, [&](auto &arg) {return TiledMma::make_fragment_A(arg);}, ComputeCopyAtomA{});
|
||||
|
||||
return cute::make_tuple(gA_mkl, dst_copy_A, tAsA, tAsACompute,
|
||||
gB_nkl);
|
||||
}
|
||||
|
||||
/// Perform a collective-scoped matrix multiply-accumulate
|
||||
/// Consumer Perspective
|
||||
template <
|
||||
class FrgEngine, class FrgLayout,
|
||||
class TensorA, class TensorB
|
||||
>
|
||||
CUTLASS_DEVICE auto
|
||||
mma(
|
||||
Transform2MmaPipeline transform2mma_pipeline,
|
||||
Transform2MmaPipelineState transform2mma_pipeline_consumer_state,
|
||||
Mma2AccumPipeline mma2accum_pipeline,
|
||||
Mma2AccumPipelineState mma2accum_pipeline_producer_state,
|
||||
cute::Tensor<FrgEngine, FrgLayout> const& accumulators,
|
||||
cute::tuple<TensorA, TensorB> const& input_operands,
|
||||
int k_tile_count
|
||||
) {
|
||||
TiledMma tiled_mma;
|
||||
|
||||
auto curr_transform2mma_pipeline_consumer_state = transform2mma_pipeline_consumer_state;
|
||||
auto next_transform2mma_pipeline_consumer_state = transform2mma_pipeline_consumer_state;
|
||||
|
||||
uint32_t skip_wait = (k_tile_count <= 0);
|
||||
auto transform2mma_flag = transform2mma_pipeline.consumer_try_wait(next_transform2mma_pipeline_consumer_state, skip_wait);
|
||||
++next_transform2mma_pipeline_consumer_state;
|
||||
|
||||
|
||||
// tCrA : (MMA), MMA_M, MMA_K, SmemStage (In SMEM or TMEM)
|
||||
// We use SMEM stages to match #buffers in Load <-> Convert
|
||||
// tCrB : (MMA), MMA_N, MMA_K, SmemStages (In SMEM)
|
||||
auto const [tCrA, tCrB] = input_operands;
|
||||
|
||||
int remaining_accum_promotions = k_tile_count;
|
||||
uint32_t mma2accum_skip_wait = (remaining_accum_promotions <= 0);
|
||||
auto mma2accum_flag = mma2accum_pipeline.producer_try_acquire(mma2accum_pipeline_producer_state, mma2accum_skip_wait);
|
||||
mma2accum_pipeline.producer_acquire(mma2accum_pipeline_producer_state, mma2accum_flag);
|
||||
auto curr_mma2accum_pipeline_producer_state = mma2accum_pipeline_producer_state;
|
||||
++mma2accum_pipeline_producer_state;
|
||||
|
||||
// No accumulator addition to the k_tile initially
|
||||
tiled_mma.accumulate_ = UMMA::ScaleOut::Zero;
|
||||
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
|
||||
transform2mma_pipeline.consumer_wait(curr_transform2mma_pipeline_consumer_state, transform2mma_flag);
|
||||
|
||||
int transform2mma_pipeline_consumer_state_index = curr_transform2mma_pipeline_consumer_state.index(); //read_stage
|
||||
int mma2accum_pipeline_producer_state_index = curr_mma2accum_pipeline_producer_state.index(); //write_stage
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
|
||||
|
||||
auto tCtC = accumulators(_,_,_,mma2accum_pipeline_producer_state_index);
|
||||
|
||||
auto tCrA0 = tCrA(_,_,_,transform2mma_pipeline_consumer_state_index);
|
||||
auto tCrB0 = tCrB(_,_,_,transform2mma_pipeline_consumer_state_index);
|
||||
|
||||
cute::gemm(tiled_mma, tCrA0(_,_,k_block), tCrB0(_,_,k_block), tCtC); // A[0]*B[0]
|
||||
tiled_mma.accumulate_ = UMMA::ScaleOut::One;
|
||||
|
||||
}
|
||||
|
||||
transform2mma_pipeline.consumer_release(curr_transform2mma_pipeline_consumer_state);
|
||||
|
||||
skip_wait = (k_tile_count <= 1);
|
||||
transform2mma_flag = transform2mma_pipeline.consumer_try_wait(next_transform2mma_pipeline_consumer_state, skip_wait);
|
||||
|
||||
curr_transform2mma_pipeline_consumer_state = next_transform2mma_pipeline_consumer_state;
|
||||
++next_transform2mma_pipeline_consumer_state;
|
||||
}
|
||||
|
||||
mma2accum_pipeline.producer_commit(curr_mma2accum_pipeline_producer_state);
|
||||
|
||||
return cute::make_tuple(curr_transform2mma_pipeline_consumer_state, mma2accum_pipeline_producer_state);
|
||||
}
|
||||
|
||||
template<class FrgEngine, class FrgLayout>
|
||||
CUTLASS_DEVICE auto
|
||||
mma_init(cute::Tensor<FrgEngine, FrgLayout> const& accumulators, TensorStorage& shared_storage) const {
|
||||
TiledMma tiled_mma;
|
||||
|
||||
auto get_tCrA = [&] () constexpr {
|
||||
if constexpr (cute::is_base_of<cute::UMMA::DescriptorIterator, typename TiledMma::FrgTypeA>::value) {
|
||||
Tensor sACompute = make_tensor(make_smem_ptr(shared_storage.compute.smem_ACompute.begin()), SmemLayoutACompute{});
|
||||
return tiled_mma.make_fragment_A(sACompute);
|
||||
}
|
||||
else {
|
||||
auto tCrA = tiled_mma.make_fragment_A(shape(SmemLayoutACompute{}));
|
||||
tCrA.data() = accumulators.data().get() + cutlass::detail::find_tmem_tensor_col_offset(accumulators);
|
||||
return tCrA;
|
||||
}
|
||||
};
|
||||
|
||||
Tensor tCrA = get_tCrA();
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_storage.input.smem_B.begin()), SmemLayoutB{});
|
||||
Tensor tCrB = tiled_mma.make_fragment_B(sB);
|
||||
return cute::make_tuple(tCrA, tCrB);
|
||||
}
|
||||
|
||||
template<class FrgEngine, class FrgLayout, class TmemCopyAtom, class EpilogueTile>
|
||||
CUTLASS_DEVICE auto
|
||||
accum_init(cute::Tensor<FrgEngine, FrgLayout> const& accumulators, TmemCopyAtom tmem_cp_atom, EpilogueTile epilogue_tile) {
|
||||
return accumulators;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class ProblemShape_MNKL>
|
||||
CUTLASS_DEVICE
|
||||
constexpr auto
|
||||
tile_input_tensors(Params const& params, ProblemShape_MNKL const& problem_shape_MNKL) const {
|
||||
using X = cute::Underscore;
|
||||
// Separate out problem shape for convenience
|
||||
auto [M,N,K,L] = problem_shape_MNKL;
|
||||
|
||||
// Represent the full tensors -- get these from TMA
|
||||
Tensor mA_mkl = observed_tma_load_a_->get_tma_tensor(make_shape(M,K,L));
|
||||
Tensor mB_nkl = observed_tma_load_b_->get_tma_tensor(make_shape(N,K,L));
|
||||
|
||||
// Tile the tensors and defer the slice
|
||||
Tensor gA_mkl = local_tile(mA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{});
|
||||
Tensor gB_nkl = local_tile(mB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{});
|
||||
|
||||
return cute::make_tuple(gA_mkl, gB_nkl);
|
||||
}
|
||||
|
||||
typename Params::TMA_A const* observed_tma_load_a_ = nullptr;
|
||||
typename Params::TMA_B const* observed_tma_load_b_ = nullptr;
|
||||
|
||||
ClusterShape cluster_shape_;
|
||||
uint32_t block_rank_in_cluster_;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::collective
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
File diff suppressed because it is too large
Load Diff
@@ -285,14 +285,14 @@ struct CollectiveMma<
|
||||
using TMA_A = decltype(make_tma_copy(
|
||||
GmemTiledCopyA{},
|
||||
make_tensor(recast_ptr<TmaInternalElementA>(nullptr), repeat_like(StrideA{}, int32_t(0)), StrideA{}),
|
||||
SmemLayoutA{}(_,_,0),
|
||||
SmemLayoutA{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<0>(TileShape{}), shape<2>(TileShape{})),
|
||||
_1{})); // No programmatic multicast
|
||||
// Assumption: StrideB is congruent with Problem_NK
|
||||
using TMA_B = decltype(make_tma_copy(
|
||||
GmemTiledCopyB{},
|
||||
make_tensor(recast_ptr<TmaInternalElementB>(nullptr), repeat_like(StrideB{}, int32_t(0)), StrideB{}),
|
||||
SmemLayoutB{}(_,_,0),
|
||||
SmemLayoutB{}(_,_,cute::Int<0>{}),
|
||||
make_shape(shape<1>(TileShape{}), shape<2>(TileShape{})),
|
||||
_1{})); // No programmatic multicast
|
||||
|
||||
|
||||
+342
-131
@@ -46,6 +46,8 @@
|
||||
#include "cute/tensor_predicate.hpp"
|
||||
#include "cute/numeric/arithmetic_tuple.hpp"
|
||||
|
||||
#include "cutlass/detail/blockwise_scale_layout.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
@@ -57,14 +59,11 @@ template <
|
||||
int Stages,
|
||||
class ClusterShape,
|
||||
class KernelSchedule,
|
||||
int ScaleGranularityM_,
|
||||
int ScaleGranularityN_,
|
||||
int ScalePromotionInterval_,
|
||||
class TileShape_,
|
||||
class ElementA_,
|
||||
class StrideA_,
|
||||
class StridePairA_,
|
||||
class ElementB_,
|
||||
class StrideB_,
|
||||
class StridePairB_,
|
||||
class TiledMma_,
|
||||
class GmemTiledCopyA_,
|
||||
class SmemLayoutAtomA_,
|
||||
@@ -75,12 +74,12 @@ template <
|
||||
class SmemCopyAtomB_,
|
||||
class TransformB_>
|
||||
struct CollectiveMma<
|
||||
MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<Stages, ClusterShape, KernelSchedule, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>,
|
||||
MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<Stages, ClusterShape, KernelSchedule>,
|
||||
TileShape_,
|
||||
ElementA_,
|
||||
StrideA_,
|
||||
StridePairA_,
|
||||
ElementB_,
|
||||
StrideB_,
|
||||
StridePairB_,
|
||||
TiledMma_,
|
||||
GmemTiledCopyA_,
|
||||
SmemLayoutAtomA_,
|
||||
@@ -94,14 +93,18 @@ struct CollectiveMma<
|
||||
//
|
||||
// Type Aliases
|
||||
//
|
||||
using DispatchPolicy = MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<Stages, ClusterShape, KernelSchedule, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>;
|
||||
using DispatchPolicy = MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<Stages, ClusterShape, KernelSchedule>;
|
||||
using TileShape = TileShape_;
|
||||
using ElementA = ElementA_;
|
||||
using StrideA = StrideA_;
|
||||
using StrideA = cute::tuple_element_t<0,StridePairA_>;
|
||||
using LayoutSFA = cute::tuple_element_t<1,StridePairA_>;
|
||||
using InternalStrideA = cute::remove_pointer_t<StrideA>;
|
||||
using InternalLayoutSFA = cute::remove_pointer_t<LayoutSFA>;
|
||||
using ElementB = ElementB_;
|
||||
using StrideB = StrideB_;
|
||||
using StrideB = cute::tuple_element_t<0,StridePairB_>;
|
||||
using LayoutSFB = cute::tuple_element_t<1,StridePairB_>;
|
||||
using InternalStrideB = cute::remove_pointer_t<StrideB>;
|
||||
using InternalLayoutSFB = cute::remove_pointer_t<LayoutSFB>;
|
||||
using TiledMma = TiledMma_;
|
||||
using ElementAccumulator = typename TiledMma::ValTypeC;
|
||||
using ElementBlockScale = ElementAccumulator;
|
||||
@@ -121,11 +124,16 @@ struct CollectiveMma<
|
||||
using PipelineParams = typename MainloopPipeline::Params;
|
||||
using CtaShape_MNK = decltype(shape_div(TileShape{}, ClusterShape{}));
|
||||
|
||||
static constexpr int NumProducerThreadEvents = 2;
|
||||
static constexpr int NumProducerThreadEvents = 33;
|
||||
|
||||
static constexpr int ScaleGranularityM = ScaleGranularityM_ == 0 ? size<0>(TileShape{}) : ScaleGranularityM_;
|
||||
static constexpr int ScaleGranularityN = ScaleGranularityN_ == 0 ? size<1>(TileShape{}) : ScaleGranularityN_;
|
||||
static constexpr int ScalePromotionInterval = ScalePromotionInterval_;
|
||||
static constexpr int ScaleGranularityM = size<0,0>(InternalLayoutSFA{});
|
||||
static constexpr int ScaleGranularityN = size<0,0>(InternalLayoutSFB{});
|
||||
static constexpr int ScaleGranularityK = size<1,0>(InternalLayoutSFA{});
|
||||
|
||||
static_assert(size<2>(TileShape{}) % ScaleGranularityK == 0);
|
||||
static_assert(ScaleGranularityK % size<2>(typename TiledMma::AtomShape_MNK{}) == 0);
|
||||
|
||||
static constexpr int ScalePromotionInterval = ScaleGranularityK / size<2>(typename TiledMma::AtomShape_MNK{});
|
||||
static_assert(ScalePromotionInterval % 4 == 0, "ScalePromotionInterval must be a multiple of 4.");
|
||||
|
||||
static constexpr int ScaleMsPerTile = size<0>(TileShape{}) / ScaleGranularityM;
|
||||
@@ -142,6 +150,10 @@ struct CollectiveMma<
|
||||
static_assert((size<0>(TileShape{}) % ScaleGranularityM) == 0, "FP8 scaling granularity must evenly divide tile shape along M.");
|
||||
static_assert((size<1>(TileShape{}) % ScaleGranularityN) == 0, "FP8 scaling granularity must evenly divide tile shape along N.");
|
||||
|
||||
using ScaleConfig = ::cutlass::detail::Sm90BlockwiseScaleConfig<ScaleGranularityM, ScaleGranularityN, ScaleGranularityK>;
|
||||
using SmemLayoutAtomSFA = decltype(ScaleConfig::smem_atom_layoutSFA(TileShape{}));
|
||||
using SmemLayoutAtomSFB = decltype(ScaleConfig::smem_atom_layoutSFB(TileShape{}));
|
||||
|
||||
// Tile along modes in a way that maximizes the TMA box size.
|
||||
using SmemLayoutA = decltype(tile_to_shape(
|
||||
SmemLayoutAtomA{},
|
||||
@@ -153,14 +165,23 @@ struct CollectiveMma<
|
||||
cute::conditional_t< ::cutlass::gemm::detail::is_major<0,StrideB>(), Step<_2,_1,_3>, Step<_1,_2,_3>>{}));
|
||||
|
||||
// Block scaling gmem-to-smem copy atom
|
||||
using BlockScaleCopyTypeA = cute::uint_byte_t<cute::min(static_cast<int>(sizeof(ElementBlockScale)) * ScaleMsPerTile, 16)>;
|
||||
using BlockScaleCopyTypeB = cute::uint_byte_t<cute::min(static_cast<int>(sizeof(ElementBlockScale)) * ScaleNsPerTile, 16)>;
|
||||
using SmemBlockScalingCopyAtomA = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<BlockScaleCopyTypeA>, ElementBlockScale>;
|
||||
using SmemBlockScalingCopyAtomB = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<BlockScaleCopyTypeB>, ElementBlockScale>;
|
||||
// we can have partial tiles in M or N, so don't vectorize those loads
|
||||
using CopyAtomSFA = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
using CopyAtomSFB = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
|
||||
static constexpr int AlignmentSFA = 1;
|
||||
static constexpr int AlignmentSFB = 1;
|
||||
|
||||
// Block scaling smem layout
|
||||
using SmemLayoutScaleA = Layout<Shape<Int<ScaleMsPerTile>, Int<DispatchPolicy::Stages>>>;
|
||||
using SmemLayoutScaleB = Layout<Shape<Int<ScaleNsPerTile>, Int<DispatchPolicy::Stages>>>;
|
||||
using SmemLayoutSFA = decltype(make_layout(
|
||||
append(shape(SmemLayoutAtomSFA{}), Int<DispatchPolicy::Stages>{}),
|
||||
append(stride(SmemLayoutAtomSFA{}), size(filter_zeros(SmemLayoutAtomSFA{})))
|
||||
));
|
||||
using SmemLayoutSFB = decltype(make_layout(
|
||||
append(shape(SmemLayoutAtomSFB{}), Int<DispatchPolicy::Stages>{}),
|
||||
append(stride(SmemLayoutAtomSFB{}), size(filter_zeros(SmemLayoutAtomSFB{})))
|
||||
));
|
||||
|
||||
|
||||
static_assert(DispatchPolicy::Stages >= 2, "Specialization requires Stages set to value 2 or more.");
|
||||
static_assert(cute::is_base_of<cute::GMMA::DescriptorIterator, typename TiledMma::FrgTypeA>::value &&
|
||||
@@ -185,8 +206,8 @@ struct CollectiveMma<
|
||||
struct TensorStorage : cute::aligned_struct<128, _0> {
|
||||
cute::array_aligned<typename TiledMma::ValTypeA, cute::cosize_v<SmemLayoutA>> smem_A;
|
||||
cute::array_aligned<typename TiledMma::ValTypeB, cute::cosize_v<SmemLayoutB>> smem_B;
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutScaleA>> smem_scale_A;
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutScaleB>> smem_scale_B;
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutSFA>> smem_SFA;
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutSFB>> smem_SFB;
|
||||
} tensors;
|
||||
|
||||
struct TensorMapStorage : cute::aligned_struct<128, _0> {
|
||||
@@ -209,8 +230,10 @@ struct CollectiveMma<
|
||||
StrideA dA;
|
||||
ElementB const** ptr_B;
|
||||
StrideB dB;
|
||||
ElementBlockScale const** ptr_scale_A;
|
||||
ElementBlockScale const** ptr_scale_B;
|
||||
ElementBlockScale const** ptr_SFA;
|
||||
LayoutSFA layout_SFA;
|
||||
ElementBlockScale const** ptr_SFB;
|
||||
LayoutSFB layout_SFB;
|
||||
};
|
||||
|
||||
// Device side kernel params
|
||||
@@ -238,8 +261,10 @@ struct CollectiveMma<
|
||||
InternalElementB const** ptr_B;
|
||||
StrideB dB;
|
||||
// Block scaling factors for A and B
|
||||
ElementBlockScale const** ptr_scale_A;
|
||||
ElementBlockScale const** ptr_scale_B;
|
||||
ElementBlockScale const** ptr_SFA;
|
||||
LayoutSFA layout_SFA;
|
||||
ElementBlockScale const** ptr_SFB;
|
||||
LayoutSFB layout_SFB;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -307,8 +332,10 @@ struct CollectiveMma<
|
||||
args.dA,
|
||||
reinterpret_cast<InternalElementB const**>(args.ptr_B),
|
||||
args.dB,
|
||||
args.ptr_scale_A,
|
||||
args.ptr_scale_B
|
||||
args.ptr_SFA,
|
||||
args.layout_SFA,
|
||||
args.ptr_SFB,
|
||||
args.layout_SFB
|
||||
};
|
||||
}
|
||||
|
||||
@@ -372,8 +399,8 @@ struct CollectiveMma<
|
||||
load_init(
|
||||
ProblemShape_MNKL const& problem_shape_MNKL,
|
||||
Params const& mainloop_params,
|
||||
ElementBlockScale const* ptr_scale_A = nullptr,
|
||||
ElementBlockScale const* ptr_scale_B = nullptr
|
||||
ElementBlockScale const* ptr_SFA = nullptr,
|
||||
ElementBlockScale const* ptr_SFB = nullptr
|
||||
) const {
|
||||
|
||||
using X = Underscore;
|
||||
@@ -383,27 +410,21 @@ struct CollectiveMma<
|
||||
|
||||
// TMA requires special handling of strides to deal with coord codomain mapping
|
||||
// Represent the full tensors -- get these from TMA
|
||||
Tensor mA_mkl = mainloop_params.tma_load_a.get_tma_tensor(make_shape(M,K,init_L)); // (m,k,l)
|
||||
Tensor mB_nkl = mainloop_params.tma_load_b.get_tma_tensor(make_shape(N,K,init_L)); // (n,k,l)
|
||||
Tensor mA_mkl = mainloop_params.tma_load_a.get_tma_tensor(make_shape(M,K,init_L)); // (m,k,l)
|
||||
Tensor mB_nkl = mainloop_params.tma_load_b.get_tma_tensor(make_shape(N,K,init_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)
|
||||
auto tK = get<3>(gA_mkl.shape());
|
||||
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)
|
||||
|
||||
// Make the tiled views of scale tensors
|
||||
auto scaleA_shape = make_shape(ceil_div(M, ScaleGranularityM), tK, L); // (scale_m,k,l)
|
||||
auto scaleB_shape = make_shape(ceil_div(N, ScaleGranularityN), tK, L); // (scale_n,k,l)
|
||||
auto scaleA_layout = make_ordered_layout(scaleA_shape, Step<_0, _1, _2>{});
|
||||
auto scaleB_layout = make_ordered_layout(scaleB_shape, Step<_0, _1, _2>{});
|
||||
|
||||
// Note that mScaleA_mkl and mScaleB_nkl are already blocked tiled in the `m` host and
|
||||
// gScaleA_mkl and gScaleB_nkl in `g` global memory are same as mScaleA_mkl and mScaleB_nkl.
|
||||
Tensor mSFA_mkl = make_tensor(make_gmem_ptr(ptr_SFA),
|
||||
ScaleConfig::tile_atom_to_shape_SFA(make_shape(M, N, K, init_L))); // (scale_m,k,l)
|
||||
Tensor mSFB_nkl = make_tensor(make_gmem_ptr(ptr_SFB),
|
||||
ScaleConfig::tile_atom_to_shape_SFB(make_shape(M, N, K, init_L))); // (scale_n,k,l)
|
||||
|
||||
Tensor mScaleA_mkl = make_tensor(make_gmem_ptr(ptr_scale_A), scaleA_layout); // (scale_m,k,l)
|
||||
Tensor mScaleB_nkl = make_tensor(make_gmem_ptr(ptr_scale_B), scaleB_layout); // (scale_n,k,l)
|
||||
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, mScaleA_mkl, mScaleB_nkl);
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, mSFA_mkl, mSFB_nkl);
|
||||
|
||||
}
|
||||
|
||||
@@ -430,10 +451,12 @@ struct CollectiveMma<
|
||||
int lane_predicate = cute::elect_one_sync();
|
||||
// Blockscaling: Tma loads for load_input and CpAsync for load_scale
|
||||
if (lane_predicate) {
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_tensors.smem_A.data()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sScaleA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_A.data()), SmemLayoutScaleA{}); // (ScaleMsPerTile,k)
|
||||
Tensor sScaleB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_B.data()), SmemLayoutScaleB{}); // (ScaleNsPerTile,k)
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_tensors.smem_A.data()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sSFA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFA.data()),
|
||||
SmemLayoutSFA{}); // (BLK_M,BLK_K,P)
|
||||
Tensor sSFB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFB.data()),
|
||||
SmemLayoutSFB{}); // (BLK_N,BLK_K,P)
|
||||
|
||||
//
|
||||
// Prepare the TMA loads for A and B
|
||||
@@ -454,26 +477,26 @@ struct CollectiveMma<
|
||||
Tensor gB = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
|
||||
// Block scaling: load_scale has scaling tensors in global memory which are not tiled
|
||||
Tensor mScaleA_mkl = get<2>(load_inputs);
|
||||
Tensor mScaleB_nkl = get<3>(load_inputs);
|
||||
Tensor mSFA_mkl = get<2>(load_inputs);
|
||||
Tensor mSFB_nkl = get<3>(load_inputs);
|
||||
|
||||
auto scales_m = get<0>(mScaleA_mkl.shape());
|
||||
auto scales_n = get<0>(mScaleB_nkl.shape());
|
||||
Tensor gSFA_mkl = local_tile(mSFA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor gSFB_nkl = local_tile(mSFB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
|
||||
Tensor gScaleA = local_tile(mScaleA_mkl, make_tile(Int<ScaleMsPerTile>{}), make_coord(m_coord,_,l_coord)); // (ScaleMsPerTile,k,1)
|
||||
Tensor gScaleB = local_tile(mScaleB_nkl, make_tile(Int<ScaleNsPerTile>{}), make_coord(n_coord,_,l_coord)); // (ScaleNsPerTile,k,1)
|
||||
Tensor gSFA_k = gSFA_mkl(_,_,m_coord,_,l_coord);
|
||||
Tensor gSFB_k = gSFB_nkl(_,_,n_coord,_,l_coord);
|
||||
|
||||
TiledCopy scale_copy_a = make_tiled_copy(SmemBlockScalingCopyAtomA{}, Layout<Shape<_1>>{}, Layout<Shape<Int<ScaleMsPerTile>>>{});
|
||||
TiledCopy scale_copy_b = make_tiled_copy(SmemBlockScalingCopyAtomB{}, Layout<Shape<_1>>{}, Layout<Shape<Int<ScaleNsPerTile>>>{});
|
||||
TiledCopy scale_copy_a = make_tiled_copy(CopyAtomSFA{}, Layout<Shape<_1>>{}, Layout<Shape<_1>>{});
|
||||
TiledCopy scale_copy_b = make_tiled_copy(CopyAtomSFB{}, Layout<Shape<_1>>{}, Layout<Shape<_1>>{});
|
||||
|
||||
ThrCopy thr_scale_copy_a = scale_copy_a.get_slice(threadIdx.x);
|
||||
ThrCopy thr_scale_copy_b = scale_copy_b.get_slice(threadIdx.x);
|
||||
ThrCopy thr_scale_copy_a = scale_copy_a.get_slice(_0{});
|
||||
ThrCopy thr_scale_copy_b = scale_copy_b.get_slice(_0{});
|
||||
|
||||
Tensor tAgA_ScaleA = thr_scale_copy_a.partition_S(gScaleA);
|
||||
Tensor tAsA_ScaleA = thr_scale_copy_a.partition_D(sScaleA);
|
||||
Tensor tSFAgSFA_k = thr_scale_copy_a.partition_S(gSFA_k);
|
||||
Tensor tSFAsSFA = thr_scale_copy_a.partition_D(sSFA);
|
||||
|
||||
Tensor tBgB_ScaleB = thr_scale_copy_b.partition_S(gScaleB);
|
||||
Tensor tBsB_ScaleB = thr_scale_copy_b.partition_D(sScaleB);
|
||||
Tensor tSFBgSFB_k = thr_scale_copy_b.partition_S(gSFB_k);
|
||||
Tensor tSFBsSFB = thr_scale_copy_b.partition_D(sSFB);
|
||||
|
||||
// Applies the mapping from block_tma_a
|
||||
Tensor tAgA = block_tma_a.partition_S(gA); // (TMA,TMA_M,TMA_K,k)
|
||||
@@ -503,8 +526,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);
|
||||
|
||||
@@ -519,11 +541,6 @@ struct CollectiveMma<
|
||||
copy(mainloop_params.tma_load_a.with(get<0>(input_tensormaps), *tma_barrier, mcast_mask_a), tAgA(_,_,_,*k_tile_iter), tAsA(_,_,_,write_stage));
|
||||
copy(mainloop_params.tma_load_b.with(get<1>(input_tensormaps), *tma_barrier, mcast_mask_b), tBgB(_,_,_,*k_tile_iter), tBsB(_,_,_,write_stage));
|
||||
|
||||
// Copy scale tensors from global memory to shared memory
|
||||
copy(scale_copy_a, tAgA_ScaleA(_,_,*k_tile_iter), tAsA_ScaleA(_,_,write_stage));
|
||||
copy(scale_copy_b, tBgB_ScaleB(_,_,*k_tile_iter), tBsB_ScaleB(_,_,write_stage));
|
||||
pipeline.producer_commit(smem_pipe_write, cutlass::arch::cpasync_barrier_arrive_noinc);
|
||||
|
||||
++k_tile_iter;
|
||||
|
||||
// Advance smem_pipe_write
|
||||
@@ -548,6 +565,117 @@ struct CollectiveMma<
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a collective-scoped matrix multiply-accumulate
|
||||
// Producer Perspective
|
||||
template <
|
||||
class TensorA, class TensorB,
|
||||
class TensorSFA, class TensorSFB,
|
||||
class KTileIterator, class BlockCoord
|
||||
>
|
||||
CUTLASS_DEVICE void
|
||||
load_auxiliary(
|
||||
Params const& mainloop_params,
|
||||
MainloopPipeline pipeline,
|
||||
PipelineState smem_pipe_write,
|
||||
cute::tuple<TensorA,
|
||||
TensorB,
|
||||
TensorSFA,
|
||||
TensorSFB> const& load_inputs,
|
||||
BlockCoord const& blk_coord,
|
||||
KTileIterator k_tile_iter, int k_tile_count,
|
||||
int thread_idx,
|
||||
uint32_t block_rank_in_cluster,
|
||||
TensorStorage& shared_tensors) {
|
||||
int lane_predicate = cute::elect_one_sync();
|
||||
Tensor sSFA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFA.data()),
|
||||
SmemLayoutSFA{}); // (BLK_M,BLK_K,P)
|
||||
Tensor sSFB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFB.data()),
|
||||
SmemLayoutSFB{}); // (BLK_N,BLK_K,P)
|
||||
|
||||
// Partition the inputs based on the current block coordinates.
|
||||
auto [m_coord, n_coord, k_coord, l_coord] = blk_coord;
|
||||
|
||||
// Block scaling: load_scale has scaling tensors in global memory which are not tiled
|
||||
Tensor mSFA_mkl = get<2>(load_inputs);
|
||||
Tensor mSFB_nkl = get<3>(load_inputs);
|
||||
Layout layoutSFA = mSFA_mkl.layout();
|
||||
Layout layoutSFB = mSFB_nkl.layout();
|
||||
|
||||
Tensor iSFA_mkl = make_identity_tensor(shape(layoutSFA)); // (m,k,l)
|
||||
Tensor iSFB_nkl = make_identity_tensor(shape(layoutSFB)); // (n,k,l)
|
||||
|
||||
|
||||
Tensor gSFA_mkl = local_tile(mSFA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor cSFA_mkl = local_tile(iSFA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor gSFB_nkl = local_tile(mSFB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
Tensor cSFB_nkl = local_tile(iSFB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
|
||||
Tensor gSFA_k = gSFA_mkl(_,_,m_coord,_,l_coord);
|
||||
Tensor cSFA_k = cSFA_mkl(_,_,m_coord,_,l_coord);
|
||||
Tensor gSFB_k = gSFB_nkl(_,_,n_coord,_,l_coord);
|
||||
Tensor cSFB_k = cSFB_nkl(_,_,n_coord,_,l_coord);
|
||||
|
||||
TiledCopy scale_copy_a = make_tiled_copy(CopyAtomSFA{}, Layout<Shape<_32>>{}, Layout<Shape<_1>>{});
|
||||
TiledCopy scale_copy_b = make_tiled_copy(CopyAtomSFB{}, Layout<Shape<_32>>{}, Layout<Shape<_1>>{});
|
||||
|
||||
ThrCopy thr_scale_copy_a = scale_copy_a.get_slice(thread_idx);
|
||||
ThrCopy thr_scale_copy_b = scale_copy_b.get_slice(thread_idx);
|
||||
|
||||
Tensor tSFAgSFA_k = thr_scale_copy_a.partition_S(gSFA_k);
|
||||
Tensor tSFAcSFA_k = thr_scale_copy_a.partition_S(cSFA_k);
|
||||
Tensor tSFAsSFA = thr_scale_copy_a.partition_D(sSFA);
|
||||
|
||||
Tensor tSFBgSFB_k = thr_scale_copy_b.partition_S(gSFB_k);
|
||||
Tensor tSFBcSFB_k = thr_scale_copy_b.partition_S(cSFB_k);
|
||||
Tensor tSFBsSFB = thr_scale_copy_b.partition_D(sSFB);
|
||||
|
||||
Tensor tSFApSFA = make_tensor<bool>(shape(filter_zeros(tSFAsSFA(_,_,_,_0{})))); // (CPY,CPY_M,CPY_K)
|
||||
Tensor tSFBpSFB = make_tensor<bool>(shape(filter_zeros(tSFBsSFB(_,_,_,_0{})))); // (CPY,CPY_N,CPY_K)
|
||||
|
||||
auto SFA_shape = shape(layoutSFA);
|
||||
auto SFB_shape = shape(layoutSFB);
|
||||
|
||||
// Mainloop
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
// LOCK smem_pipe_write for _writing_
|
||||
pipeline.producer_acquire(smem_pipe_write);
|
||||
|
||||
// Since scale granularity K is multiple of BLK_K we do not have to consider if that is OOB
|
||||
bool load_sfa = thread_idx < ScaleMsPerTile;
|
||||
Tensor tSFAcSFA = tSFAcSFA_k(_,_,_,*k_tile_iter);
|
||||
Tensor tSFAcSFA_compact = filter_zeros(tSFAcSFA);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tSFApSFA); ++i) {
|
||||
tSFApSFA(i) = load_sfa && elem_less(tSFAcSFA_compact(i), SFA_shape);
|
||||
}
|
||||
|
||||
bool load_sfb = thread_idx < ScaleNsPerTile;
|
||||
Tensor tSFBcSFB = tSFBcSFB_k(_,_,_,*k_tile_iter);
|
||||
Tensor tSFBcSFB_compact = filter_zeros(tSFBcSFB);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tSFBpSFB); ++i) {
|
||||
tSFBpSFB(i) = load_sfb && elem_less(tSFBcSFB_compact(i), SFB_shape);
|
||||
}
|
||||
|
||||
//
|
||||
// Copy gmem to smem for *k_tile_iter
|
||||
//
|
||||
int write_stage = smem_pipe_write.index();
|
||||
|
||||
// Copy scale tensors from global memory to shared memory
|
||||
copy_if(scale_copy_a, tSFApSFA, filter_zeros(tSFAgSFA_k(_,_,_,*k_tile_iter)), filter_zeros(tSFAsSFA(_,_,_,write_stage)));
|
||||
copy_if(scale_copy_b, tSFBpSFB, filter_zeros(tSFBgSFB_k(_,_,_,*k_tile_iter)), filter_zeros(tSFBsSFB(_,_,_,write_stage)));
|
||||
|
||||
pipeline.producer_commit(smem_pipe_write, cutlass::arch::cpasync_barrier_arrive_noinc);
|
||||
|
||||
++k_tile_iter;
|
||||
|
||||
// Advance smem_pipe_write
|
||||
++smem_pipe_write;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<
|
||||
class EngineAccum,
|
||||
@@ -604,20 +732,30 @@ struct CollectiveMma<
|
||||
static_assert(cute::is_void_v<SmemCopyAtomB>,
|
||||
"SM90 GMMA mainloops cannot have a non-void copy atom for smem sourced instructions.");
|
||||
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_tensors.smem_A.data()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_tensors.smem_A.data()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
|
||||
// Block scaling
|
||||
Tensor sScaleAViewAsC = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_A.data()),
|
||||
Layout<
|
||||
Shape<Shape<Int<ScaleGranularityM>, Int<ScaleMsPerTile>>, cute::tuple_element_t<1, TileShape>, Int<DispatchPolicy::Stages>>,
|
||||
Stride<Stride<_0, _1>, _0, Int<ScaleMsPerTile>>
|
||||
>{}); // ((ScaleGranularityM,ScaleMsPerTile),n,k)
|
||||
Tensor sScaleBViewAsC = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_B.data()),
|
||||
Layout<
|
||||
Shape<cute::tuple_element_t<0, TileShape>, Shape<Int<ScaleGranularityN>, Int<ScaleNsPerTile>>, Int<DispatchPolicy::Stages>>,
|
||||
Stride<_0, Stride<_0, _1>, Int<ScaleNsPerTile>>
|
||||
>{}); // (m,(ScaleGranularityN,ScaleNsPerTile),k)
|
||||
Tensor sSFA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFA.data()), make_layout(
|
||||
make_shape(shape<0>(SmemLayoutSFA{}),
|
||||
get<1>(TileShape{}),
|
||||
make_shape(shape<1>(SmemLayoutSFA{}),
|
||||
shape<2>(SmemLayoutSFA{}))),
|
||||
make_stride(stride<0>(SmemLayoutSFA{}), _0{},
|
||||
make_stride(stride<1>(SmemLayoutSFA{}),
|
||||
stride<2>(SmemLayoutSFA{})))
|
||||
)); // (BLK_M,BLK_N,(BLK_K,P))
|
||||
Tensor sSFB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFB.data()), make_layout(
|
||||
make_shape(get<0>(TileShape{}),
|
||||
shape<0>(SmemLayoutSFB{}),
|
||||
make_shape(shape<1>(SmemLayoutSFB{}),
|
||||
shape<2>(SmemLayoutSFB{}))),
|
||||
make_stride(_0{},
|
||||
stride<0>(SmemLayoutSFB{}),
|
||||
make_stride(stride<1>(SmemLayoutSFB{}),
|
||||
stride<2>(SmemLayoutSFB{})))
|
||||
)); // (BLK_M,BLK_N,(BLK_K,P))
|
||||
|
||||
|
||||
//
|
||||
// Define C accumulators and A/B partitioning
|
||||
@@ -640,8 +778,9 @@ struct CollectiveMma<
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsScaleAViewAsC = tiled_mma.get_slice(thread_idx).partition_C(sScaleAViewAsC); // (MMA,MMA_M,MMA_N,PIPE), `thread_mma` above is correct when partitioning A and B, but it is not correct when partitioning C.
|
||||
Tensor tCsScaleBViewAsC = tiled_mma.get_slice(thread_idx).partition_C(sScaleBViewAsC); // (MMA,MMA_M,MMA_N,PIPE), `thread_mma` above is correct when partitioning A and B, but it is not correct when partitioning C.
|
||||
Tensor tCsSFA = tiled_mma.get_slice(thread_idx).partition_C(sSFA); // (MMA,MMA_M,MMA_N,(MMA_K,PIPE))
|
||||
Tensor tCsSFB = tiled_mma.get_slice(thread_idx).partition_C(sSFB); // (MMA,MMA_M,MMA_N,(MMA_K,PIPE))
|
||||
|
||||
|
||||
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)
|
||||
@@ -667,8 +806,9 @@ struct CollectiveMma<
|
||||
PipelineState smem_pipe_release = smem_pipe_read;
|
||||
|
||||
// Per block scale values for operand A and B
|
||||
Tensor tCrScaleAViewAsC = make_tensor_like<ElementBlockScale>(tCsScaleAViewAsC(_, _, _, 0)); // (MMA,MMA_M,MMA_N)
|
||||
Tensor tCrScaleBViewAsC = make_tensor_like<ElementBlockScale>(tCsScaleBViewAsC(_, _, _, 0)); // (MMA,MMA_M,MMA_N)
|
||||
// Since scale factors always broadcast across MMA_K we slice that away
|
||||
Tensor tCrSFA = make_tensor_like<ElementBlockScale>(tCsSFA(_, _, _, _0{})); // (MMA,MMA_M,MMA_N)
|
||||
Tensor tCrSFB = make_tensor_like<ElementBlockScale>(tCsSFB(_, _, _, _0{})); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
// Prologue GMMAs
|
||||
int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
@@ -676,10 +816,6 @@ struct CollectiveMma<
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
// fence_operand();
|
||||
GmmaFP8Accumulation accumulation(accum, ScalePromotionInterval, size<2>(tCrA));
|
||||
warpgroup_fence_operand(accumulation());
|
||||
|
||||
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);
|
||||
@@ -697,49 +833,122 @@ struct CollectiveMma<
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
// Load per block scale values from shared memory to registers
|
||||
copy(tCsScaleAViewAsC(_, _, _, read_stage), tCrScaleAViewAsC);
|
||||
copy(tCsScaleBViewAsC(_, _, _, read_stage), tCrScaleBViewAsC);
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrScaleAViewAsC.data()[0] = tCrScaleAViewAsC.data()[0] * tCrScaleBViewAsC.data()[0];
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrScaleBViewAsC.data()[0];
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleAViewAsC); i++) {
|
||||
tCrScaleAViewAsC.data()[i] = tCrScaleAViewAsC.data()[i] * scale_b;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleBViewAsC); i++) {
|
||||
tCrScaleBViewAsC.data()[i] = tCrScaleBViewAsC.data()[i] * scale_a;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// (V,M) x (V,N) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accumulation());
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
|
||||
warpgroup_commit_batch();
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrScaleAViewAsC` and `tCrScaleBViewAsC`
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
++smem_pipe_read;
|
||||
}
|
||||
|
||||
warpgroup_fence_operand(accumulation());
|
||||
|
||||
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);
|
||||
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
if (accumulation.prepare_if_needed()) {
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Always zero out the accumulator for finest granularity
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
}
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
// Load per block scale values from shared memory to registers
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
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) x (V,N) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accumulation());
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
|
||||
warpgroup_commit_batch();
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
++smem_pipe_read;
|
||||
@@ -763,26 +972,28 @@ struct CollectiveMma<
|
||||
int read_stage = smem_pipe_read.index();
|
||||
// fence_operand();
|
||||
// Load per block scale values from shared memory to registers (at most twice per block along M and/or N)
|
||||
copy(tCsScaleAViewAsC(_, _, _, read_stage), tCrScaleAViewAsC);
|
||||
copy(tCsScaleBViewAsC(_, _, _, read_stage), tCrScaleBViewAsC);
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrScaleAViewAsC.data()[0] = tCrScaleAViewAsC.data()[0] * tCrScaleBViewAsC.data()[0];
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrScaleBViewAsC.data()[0];
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleAViewAsC); i++) {
|
||||
tCrScaleAViewAsC.data()[i] = tCrScaleAViewAsC.data()[i] * scale_b;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrScaleAViewAsC.data()[0];
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleBViewAsC); i++) {
|
||||
tCrScaleBViewAsC.data()[i] = tCrScaleBViewAsC.data()[i] * scale_a;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
if (accumulation.prepare_if_needed()) {
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
@@ -799,7 +1010,7 @@ struct CollectiveMma<
|
||||
// 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)
|
||||
// (V,M) x (V,N) => (V,M,N)
|
||||
cute::gemm(tiled_mma, tCrA(_,_,k_block,read_stage), tCrB(_,_,k_block,read_stage), accumulation());
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
@@ -809,19 +1020,19 @@ struct CollectiveMma<
|
||||
warpgroup_wait<K_PIPE_MMAS>();
|
||||
warpgroup_fence_operand(accumulation());
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrScaleAViewAsC` and `tCrScaleBViewAsC`
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
// UNLOCK smem_pipe_release, done _computing_ on it
|
||||
@@ -834,17 +1045,17 @@ struct CollectiveMma<
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
// residues only exists when granularity is not the finnest
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
accumulation.scale_residue_if_needed(scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleAViewAsC);
|
||||
accumulation.scale_residue_if_needed(tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleBViewAsC);
|
||||
accumulation.scale_residue_if_needed(tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
accumulation.scale_residue_if_needed(tCrSFA, tCrSFB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,8 +1225,8 @@ struct CollectiveMma<
|
||||
return load_init(
|
||||
problem_shape_mnkl,
|
||||
mainloop_params,
|
||||
mainloop_params.ptr_scale_A[next_batch],
|
||||
mainloop_params.ptr_scale_B[next_batch]
|
||||
mainloop_params.ptr_SFA[next_batch],
|
||||
mainloop_params.ptr_SFB[next_batch]
|
||||
);
|
||||
} else {
|
||||
auto [gA_mkl, gB_nkl, mScaleA_mkl, mScaleB_nkl] = input_tensors;
|
||||
@@ -1023,8 +1234,8 @@ struct CollectiveMma<
|
||||
auto scaleA_layout = mScaleA_mkl.layout();
|
||||
auto scaleB_layout = mScaleB_nkl.layout();
|
||||
|
||||
mScaleA_mkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_scale_A[next_batch]), scaleA_layout); // (m,ScaleMsPerTile,k,l)
|
||||
mScaleB_nkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_scale_B[next_batch]), scaleB_layout); // (n,ScaleNsPerTile,k,l)
|
||||
mScaleA_mkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_SFA[next_batch]), scaleA_layout); // (m,ScaleMsPerTile,k,l)
|
||||
mScaleB_nkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_SFB[next_batch]), scaleB_layout); // (n,ScaleNsPerTile,k,l)
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, mScaleA_mkl, mScaleB_nkl);
|
||||
}
|
||||
}
|
||||
|
||||
+252
-155
@@ -45,6 +45,8 @@
|
||||
#include "cute/tensor_predicate.hpp"
|
||||
#include "cute/numeric/arithmetic_tuple.hpp"
|
||||
|
||||
#include "cutlass/detail/blockwise_scale_layout.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::gemm::collective {
|
||||
@@ -57,14 +59,11 @@ template <
|
||||
int Stages,
|
||||
class ClusterShape,
|
||||
class KernelSchedule,
|
||||
int ScaleGranularityM_,
|
||||
int ScaleGranularityN_,
|
||||
int ScalePromotionInterval_,
|
||||
class TileShape_,
|
||||
class ElementA_,
|
||||
class StrideA_,
|
||||
class StridePairA_,
|
||||
class ElementB_,
|
||||
class StrideB_,
|
||||
class StridePairB_,
|
||||
class TiledMma_,
|
||||
class GmemTiledCopyA_,
|
||||
class SmemLayoutAtomA_,
|
||||
@@ -75,12 +74,12 @@ template <
|
||||
class SmemCopyAtomB_,
|
||||
class TransformB_>
|
||||
struct CollectiveMma<
|
||||
MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<Stages, ClusterShape, KernelSchedule, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>,
|
||||
MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<Stages, ClusterShape, KernelSchedule>,
|
||||
TileShape_,
|
||||
ElementA_,
|
||||
StrideA_,
|
||||
StridePairA_,
|
||||
ElementB_,
|
||||
StrideB_,
|
||||
StridePairB_,
|
||||
TiledMma_,
|
||||
GmemTiledCopyA_,
|
||||
SmemLayoutAtomA_,
|
||||
@@ -89,17 +88,18 @@ struct CollectiveMma<
|
||||
GmemTiledCopyB_,
|
||||
SmemLayoutAtomB_,
|
||||
SmemCopyAtomB_,
|
||||
TransformB_>
|
||||
{
|
||||
TransformB_> {
|
||||
//
|
||||
// Type Aliases
|
||||
//
|
||||
using DispatchPolicy = MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<Stages, ClusterShape, KernelSchedule, ScaleGranularityM_, ScaleGranularityN_, ScalePromotionInterval_>;
|
||||
using DispatchPolicy = MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8<Stages, ClusterShape, KernelSchedule>;
|
||||
using TileShape = TileShape_;
|
||||
using ElementA = ElementA_;
|
||||
using StrideA = StrideA_;
|
||||
using StrideA = cute::tuple_element_t<0,StridePairA_>;
|
||||
using LayoutSFA = cute::tuple_element_t<1,StridePairA_>;
|
||||
using ElementB = ElementB_;
|
||||
using StrideB = StrideB_;
|
||||
using StrideB = cute::tuple_element_t<0,StridePairB_>;
|
||||
using LayoutSFB = cute::tuple_element_t<1,StridePairB_>;
|
||||
using TiledMma = TiledMma_;
|
||||
using ElementAccumulator = typename TiledMma::ValTypeC;
|
||||
using ElementBlockScale = ElementAccumulator;
|
||||
@@ -118,12 +118,17 @@ struct CollectiveMma<
|
||||
using PipelineState = cutlass::PipelineState<DispatchPolicy::Stages>;
|
||||
using PipelineParams = typename MainloopPipeline::Params;
|
||||
|
||||
// Two threads per CTA are producers (1 for operand tile `tma`, and 32 for scales `cp.async`)
|
||||
// 33 threads per CTA are producers (1 for operand tile `tma`, and 32 for scales `cp.async`)
|
||||
static constexpr int NumProducerThreadEvents = 33;
|
||||
|
||||
static constexpr int ScaleGranularityM = ScaleGranularityM_ == 0 ? size<0>(TileShape{}) : ScaleGranularityM_;
|
||||
static constexpr int ScaleGranularityN = ScaleGranularityN_ == 0 ? size<1>(TileShape{}) : ScaleGranularityN_;
|
||||
static constexpr int ScalePromotionInterval = ScalePromotionInterval_;
|
||||
static constexpr int ScaleGranularityM = size<0,0>(LayoutSFA{});
|
||||
static constexpr int ScaleGranularityN = size<0,0>(LayoutSFB{});
|
||||
static constexpr int ScaleGranularityK = size<1,0>(LayoutSFA{});
|
||||
|
||||
static_assert(size<2>(TileShape{}) % ScaleGranularityK == 0);
|
||||
static_assert(ScaleGranularityK % size<2>(typename TiledMma::AtomShape_MNK{}) == 0);
|
||||
|
||||
static constexpr int ScalePromotionInterval = ScaleGranularityK / size<2>(typename TiledMma::AtomShape_MNK{});
|
||||
static_assert(ScalePromotionInterval % 4 == 0, "ScalePromotionInterval must be a multiple of 4.");
|
||||
static constexpr int ScaleMsPerTile = size<0>(TileShape{}) / ScaleGranularityM;
|
||||
static constexpr int ScaleNsPerTile = size<1>(TileShape{}) / ScaleGranularityN;
|
||||
@@ -139,6 +144,10 @@ struct CollectiveMma<
|
||||
static_assert((size<0>(TileShape{}) % ScaleGranularityM) == 0, "FP8 scaling granularity must evenly divide tile shape along M.");
|
||||
static_assert((size<1>(TileShape{}) % ScaleGranularityN) == 0, "FP8 scaling granularity must evenly divide tile shape along N.");
|
||||
|
||||
using ScaleConfig = ::cutlass::detail::Sm90BlockwiseScaleConfig<ScaleGranularityM, ScaleGranularityN, ScaleGranularityK>;
|
||||
using SmemLayoutAtomSFA = decltype(ScaleConfig::smem_atom_layoutSFA(TileShape{}));
|
||||
using SmemLayoutAtomSFB = decltype(ScaleConfig::smem_atom_layoutSFB(TileShape{}));
|
||||
|
||||
// Tile along modes in a way that maximizes the TMA box size.
|
||||
using SmemLayoutA = decltype(tile_to_shape(
|
||||
SmemLayoutAtomA{},
|
||||
@@ -151,12 +160,22 @@ struct CollectiveMma<
|
||||
|
||||
// Block scaling gmem-to-smem copy atom
|
||||
// we can have partial tiles in M or N, so don't vectorize those loads
|
||||
using SmemBlockScalingCopyAtomA = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
using SmemBlockScalingCopyAtomB = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
using CopyAtomSFA = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
using CopyAtomSFB = Copy_Atom<SM80_CP_ASYNC_CACHEALWAYS<ElementBlockScale>, ElementBlockScale>;
|
||||
|
||||
static constexpr int AlignmentSFA = 1;
|
||||
static constexpr int AlignmentSFB = 1;
|
||||
|
||||
// Block scaling smem layout
|
||||
using SmemLayoutScaleA = Layout<Shape<Int<ScaleMsPerTile>, Int<DispatchPolicy::Stages>>>;
|
||||
using SmemLayoutScaleB = Layout<Shape<Int<ScaleNsPerTile>, Int<DispatchPolicy::Stages>>>;
|
||||
using SmemLayoutSFA = decltype(make_layout(
|
||||
append(shape(SmemLayoutAtomSFA{}), Int<DispatchPolicy::Stages>{}),
|
||||
append(stride(SmemLayoutAtomSFA{}), size(filter_zeros(SmemLayoutAtomSFA{})))
|
||||
));
|
||||
using SmemLayoutSFB = decltype(make_layout(
|
||||
append(shape(SmemLayoutAtomSFB{}), Int<DispatchPolicy::Stages>{}),
|
||||
append(stride(SmemLayoutAtomSFB{}), size(filter_zeros(SmemLayoutAtomSFB{})))
|
||||
));
|
||||
|
||||
|
||||
static_assert(DispatchPolicy::Stages >= 2, "Specialization requires Stages set to value 1 or more.");
|
||||
static_assert(cute::is_base_of<cute::GMMA::DescriptorIterator, typename TiledMma::FrgTypeA>::value &&
|
||||
@@ -174,8 +193,8 @@ struct CollectiveMma<
|
||||
struct TensorStorage : cute::aligned_struct<128> {
|
||||
cute::array_aligned<typename TiledMma::ValTypeA, cute::cosize_v<SmemLayoutA>> smem_A; // mxk
|
||||
cute::array_aligned<typename TiledMma::ValTypeB, cute::cosize_v<SmemLayoutB>> smem_B; // nxk
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutScaleA>> smem_scale_A; // ScaleMsPerTile x k
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutScaleB>> smem_scale_B; // ScaleNsPerTile x k
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutSFA>> smem_SFA; // ScaleMsPerTile x k
|
||||
cute::array_aligned<ElementBlockScale, cute::cosize_v<SmemLayoutSFB>> smem_SFB; // ScaleNsPerTile x k
|
||||
} tensors;
|
||||
|
||||
using PipelineStorage = typename MainloopPipeline::SharedStorage;
|
||||
@@ -191,8 +210,10 @@ struct CollectiveMma<
|
||||
ElementB const* ptr_B;
|
||||
StrideB dB;
|
||||
uint32_t mma_promotion_interval = 4;
|
||||
ElementBlockScale const* ptr_scale_A;
|
||||
ElementBlockScale const* ptr_scale_B;
|
||||
ElementBlockScale const* ptr_SFA;
|
||||
LayoutSFA layout_SFA;
|
||||
ElementBlockScale const* ptr_SFB;
|
||||
LayoutSFB layout_SFB;
|
||||
};
|
||||
|
||||
// Device side kernel params
|
||||
@@ -217,8 +238,10 @@ struct CollectiveMma<
|
||||
uint32_t tma_transaction_bytes_mk = TmaTransactionBytesMK;
|
||||
uint32_t tma_transaction_bytes_nk = TmaTransactionBytesNK;
|
||||
// Block scaling factors for A and B
|
||||
ElementBlockScale const* ptr_scale_A;
|
||||
ElementBlockScale const* ptr_scale_B;
|
||||
ElementBlockScale const* ptr_SFA;
|
||||
LayoutSFA layout_SFA;
|
||||
ElementBlockScale const* ptr_SFB;
|
||||
LayoutSFB layout_SFB;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -261,8 +284,10 @@ struct CollectiveMma<
|
||||
transaction_bytes,
|
||||
transaction_bytes_mk,
|
||||
transaction_bytes_nk,
|
||||
args.ptr_scale_A,
|
||||
args.ptr_scale_B
|
||||
args.ptr_SFA,
|
||||
args.layout_SFA,
|
||||
args.ptr_SFB,
|
||||
args.layout_SFB
|
||||
};
|
||||
}
|
||||
|
||||
@@ -325,27 +350,19 @@ struct CollectiveMma<
|
||||
|
||||
// TMA requires special handling of strides to deal with coord codomain mapping
|
||||
// Represent the full tensors -- get these from TMA
|
||||
Tensor mA_mkl = mainloop_params.tma_load_a.get_tma_tensor(make_shape(M,K,L)); // (m,k,l)
|
||||
Tensor mB_nkl = mainloop_params.tma_load_b.get_tma_tensor(make_shape(N,K,L)); // (n,k,l)
|
||||
Tensor mA_mkl = mainloop_params.tma_load_a.get_tma_tensor(make_shape(M,K,L)); // (m,k,l)
|
||||
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)
|
||||
|
||||
auto tK = get<3>(gA_mkl.shape());
|
||||
|
||||
// Make the tiled views of scale tensors
|
||||
auto scaleA_shape = make_shape(ceil_div(M, ScaleGranularityM), tK, L); // (scale_m,k,l)
|
||||
auto scaleA_layout = make_ordered_layout(scaleA_shape, Step<_0, _1, _2>{});
|
||||
auto scaleB_shape = make_shape(ceil_div(N, ScaleGranularityN), tK, L); // (scale_n,k,l)
|
||||
auto scaleB_layout = make_ordered_layout(scaleB_shape, Step<_0, _1, _2>{});
|
||||
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)
|
||||
|
||||
// Note that mScaleA_mkl and mScaleB_nkl are already blocked tiled in the `m` host and
|
||||
// gScaleA_mkl and gScaleB_nkl in `g` global memory are same as mScaleA_mkl and mScaleB_nkl.
|
||||
Tensor mScaleA_mkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_scale_A), scaleA_layout); // (scale_m,k,l)
|
||||
Tensor mScaleB_nkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_scale_B), scaleB_layout); // (scale_n,k,l)
|
||||
Tensor mSFA_mkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_SFA), mainloop_params.layout_SFA); // (scale_m,k,l)
|
||||
Tensor mSFB_nkl = make_tensor(make_gmem_ptr(mainloop_params.ptr_SFB), mainloop_params.layout_SFB); // (scale_n,k,l)
|
||||
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, mScaleA_mkl, mScaleB_nkl);
|
||||
return cute::make_tuple(gA_mkl, gB_nkl, mSFA_mkl, mSFB_nkl);
|
||||
}
|
||||
|
||||
/// Perform a collective-scoped matrix multiply-accumulate
|
||||
@@ -370,8 +387,8 @@ struct CollectiveMma<
|
||||
// Blockscaling: Tma loads for load_input and CpAsync for load_scale
|
||||
Tensor sA = make_tensor(make_smem_ptr(shared_tensors.smem_A.data()), SmemLayoutA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
Tensor sScaleA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_A.data()), SmemLayoutScaleA{}); // (ScaleMsPerTile,k)
|
||||
Tensor sScaleB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_B.data()), SmemLayoutScaleB{}); // (ScaleNsPerTile,k)
|
||||
Tensor sSFA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFA.data()), SmemLayoutSFA{}); // (BLK_M,BLK_K,PIPE)
|
||||
Tensor sSFB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFB.data()), SmemLayoutSFB{}); // (BLK_M,BLK_K,PIPE)
|
||||
|
||||
//
|
||||
// Prepare the TMA loads for A and B
|
||||
@@ -393,63 +410,46 @@ struct CollectiveMma<
|
||||
|
||||
|
||||
// Block scaling: load_scale has scaling tensors in global memory which are not tiled
|
||||
Tensor mScaleA_mkl = get<2>(load_inputs);
|
||||
Tensor mScaleB_nkl = get<3>(load_inputs);
|
||||
auto scales_m = get<0>(mScaleA_mkl.shape());
|
||||
auto scales_n = get<0>(mScaleB_nkl.shape());
|
||||
Tensor mSFA_mkl = get<2>(load_inputs);
|
||||
Tensor mSFB_nkl = get<3>(load_inputs);
|
||||
|
||||
Tensor cScaleA_mkl = make_identity_tensor(mScaleA_mkl.shape());
|
||||
Tensor cScaleB_nkl = make_identity_tensor(mScaleB_nkl.shape());
|
||||
Tensor iSFA_mkl = make_identity_tensor(shape(mainloop_params.layout_SFA)); // (m,k,l)
|
||||
Tensor iSFB_nkl = make_identity_tensor(shape(mainloop_params.layout_SFB)); // (n,k,l)
|
||||
|
||||
Tensor gScaleA = local_tile(
|
||||
mScaleA_mkl, make_tile(Int<ScaleMsPerTile>{}),
|
||||
make_coord(m_coord,_,l_coord)); // (ScaleMsPerTile,k,1)
|
||||
Tensor cScaleA = local_tile(
|
||||
cScaleA_mkl, make_tile(Int<ScaleMsPerTile>{}),
|
||||
make_coord(m_coord,_,l_coord));
|
||||
Tensor gScaleB = local_tile(
|
||||
mScaleB_nkl, make_tile(Int<ScaleNsPerTile>{}),
|
||||
make_coord(n_coord,_,l_coord)); // (ScaleNsPerTile,k,1)
|
||||
Tensor cScaleB = local_tile(
|
||||
cScaleB_nkl, make_tile(Int<ScaleNsPerTile>{}),
|
||||
make_coord(n_coord,_,l_coord));
|
||||
Tensor gSFA_mkl = local_tile(mSFA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor cSFA_mkl = local_tile(iSFA_mkl, TileShape{}, make_coord(_,_,_), Step<_1, X,_1>{}); // (BLK_M,BLK_K,m,k,l)
|
||||
Tensor gSFB_nkl = local_tile(mSFB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
Tensor cSFB_nkl = local_tile(iSFB_nkl, TileShape{}, make_coord(_,_,_), Step< X,_1,_1>{}); // (BLK_N,BLK_K,n,k,l)
|
||||
|
||||
TiledCopy scale_copy_a = make_tiled_copy(SmemBlockScalingCopyAtomA{},
|
||||
Tensor gSFA_k = gSFA_mkl(_,_,m_coord,_,l_coord);
|
||||
Tensor cSFA_k = cSFA_mkl(_,_,m_coord,_,l_coord);
|
||||
Tensor gSFB_k = gSFB_nkl(_,_,n_coord,_,l_coord);
|
||||
Tensor cSFB_k = cSFB_nkl(_,_,n_coord,_,l_coord);
|
||||
|
||||
TiledCopy scale_copy_a = make_tiled_copy(CopyAtomSFA{},
|
||||
Layout<Shape<_32>>{}, Layout<Shape<_1>>{});
|
||||
TiledCopy scale_copy_b = make_tiled_copy(SmemBlockScalingCopyAtomB{},
|
||||
TiledCopy scale_copy_b = make_tiled_copy(CopyAtomSFB{},
|
||||
Layout<Shape<_32>>{}, Layout<Shape<_1>>{});
|
||||
ThrCopy thr_scale_copy_a = scale_copy_a.get_slice(threadIdx.x);
|
||||
ThrCopy thr_scale_copy_b = scale_copy_b.get_slice(threadIdx.x);
|
||||
ThrCopy thr_scale_copy_a = scale_copy_a.get_slice(thread_idx);
|
||||
ThrCopy thr_scale_copy_b = scale_copy_b.get_slice(thread_idx);
|
||||
|
||||
Tensor tAgA_ScaleA = thr_scale_copy_a.partition_S(gScaleA);
|
||||
Tensor tAcA_ScaleA = thr_scale_copy_a.partition_S(cScaleA);
|
||||
Tensor tAsA_ScaleA = thr_scale_copy_a.partition_D(sScaleA);
|
||||
Tensor tSFAgSFA_k = thr_scale_copy_a.partition_S(gSFA_k);
|
||||
Tensor tSFAcSFA_k = thr_scale_copy_a.partition_S(cSFA_k);
|
||||
Tensor tSFAsSFA = thr_scale_copy_a.partition_D(sSFA);
|
||||
|
||||
Tensor tBgB_ScaleB = thr_scale_copy_b.partition_S(gScaleB);
|
||||
Tensor tBcB_ScaleB = thr_scale_copy_b.partition_S(cScaleB);
|
||||
Tensor tBsB_ScaleB = thr_scale_copy_b.partition_D(sScaleB);
|
||||
Tensor tSFBgSFB_k = thr_scale_copy_b.partition_S(gSFB_k);
|
||||
Tensor tSFBcSFB_k = thr_scale_copy_b.partition_S(cSFB_k);
|
||||
Tensor tSFBsSFB = thr_scale_copy_b.partition_D(sSFB);
|
||||
|
||||
// Applies the mapping from block_tma_a
|
||||
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 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 tBsB = block_tma_b.partition_D(sB); // (TMA,TMA_N,TMA_K,PIPE)
|
||||
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)
|
||||
|
||||
Tensor tApA_ScaleA = make_tensor<bool>(shape(tAsA_ScaleA(_,_,0)));
|
||||
Tensor tBpB_ScaleB = make_tensor<bool>(shape(tBsB_ScaleB(_,_,0)));
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < size(tApA_ScaleA); ++i) {
|
||||
tApA_ScaleA(i) = get<0>(tAcA_ScaleA(i)) <
|
||||
std::min(scales_m, (m_coord + 1) * ScaleMsPerTile);
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < size(tBpB_ScaleB); ++i) {
|
||||
tBpB_ScaleB(i) = get<0>(tBcB_ScaleB(i)) <
|
||||
std::min(scales_n, (n_coord + 1) * ScaleNsPerTile);
|
||||
}
|
||||
Tensor tSFApSFA = make_tensor<bool>(shape(filter_zeros(tSFAsSFA(_,_,_,_0{})))); // (CPY,CPY_M,CPY_K)
|
||||
Tensor tSFBpSFB = make_tensor<bool>(shape(filter_zeros(tSFBsSFB(_,_,_,_0{})))); // (CPY,CPY_N,CPY_K)
|
||||
|
||||
uint16_t mcast_mask_a = 0;
|
||||
uint16_t mcast_mask_b = 0;
|
||||
@@ -470,12 +470,32 @@ struct CollectiveMma<
|
||||
}
|
||||
}
|
||||
|
||||
auto SFA_shape = shape(mainloop_params.layout_SFA);
|
||||
auto SFB_shape = shape(mainloop_params.layout_SFB);
|
||||
|
||||
// Mainloop
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for ( ; k_tile_count > 0; --k_tile_count) {
|
||||
// LOCK smem_pipe_write for _writing_
|
||||
pipeline.producer_acquire(smem_pipe_write);
|
||||
|
||||
// Since scale granularity K is multiple of BLK_K we do not have to consider if that is OOB
|
||||
Tensor tSFAcSFA = tSFAcSFA_k(_,_,_,*k_tile_iter);
|
||||
Tensor tSFAcSFA_compact = filter_zeros(tSFAcSFA);
|
||||
bool load_sfa = thread_idx < ScaleMsPerTile;
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tSFApSFA); ++i) {
|
||||
tSFApSFA(i) = load_sfa && elem_less(get<0>(tSFAcSFA_compact(i)), get<0>(SFA_shape));
|
||||
}
|
||||
|
||||
bool load_sfb = thread_idx < ScaleNsPerTile;
|
||||
Tensor tSFBcSFB = tSFBcSFB_k(_,_,_,*k_tile_iter);
|
||||
Tensor tSFBcSFB_compact = filter_zeros(tSFBcSFB);
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tSFBpSFB); ++i) {
|
||||
tSFBpSFB(i) = load_sfb && elem_less(get<0>(tSFBcSFB_compact(i)), get<0>(SFB_shape));
|
||||
}
|
||||
|
||||
//
|
||||
// Copy gmem to smem for *k_tile_iter
|
||||
//
|
||||
@@ -488,8 +508,8 @@ struct CollectiveMma<
|
||||
if (lane_predicate) copy(mainloop_params.tma_load_b.with(*tma_barrier, mcast_mask_b), tBgB(_,_,_,*k_tile_iter), tBsB(_,_,_,write_stage));
|
||||
|
||||
// Copy scale tensors from global memory to shared memory
|
||||
copy_if(scale_copy_a, tApA_ScaleA, tAgA_ScaleA(_,_,*k_tile_iter), tAsA_ScaleA(_,_,write_stage));
|
||||
copy_if(scale_copy_b, tBpB_ScaleB, tBgB_ScaleB(_,_,*k_tile_iter), tBsB_ScaleB(_,_,write_stage));
|
||||
copy_if(scale_copy_a, tSFApSFA, filter_zeros(tSFAgSFA_k(_,_,_,*k_tile_iter)), filter_zeros(tSFAsSFA(_,_,_,write_stage)));
|
||||
copy_if(scale_copy_b, tSFBpSFB, filter_zeros(tSFBgSFB_k(_,_,_,*k_tile_iter)), filter_zeros(tSFBsSFB(_,_,_,write_stage)));
|
||||
pipeline.producer_commit(smem_pipe_write, cutlass::arch::cpasync_barrier_arrive_noinc);
|
||||
|
||||
++k_tile_iter;
|
||||
@@ -577,16 +597,24 @@ struct CollectiveMma<
|
||||
Tensor sB = make_tensor(make_smem_ptr(shared_tensors.smem_B.data()), SmemLayoutB{}); // (BLK_N,BLK_K,PIPE)
|
||||
|
||||
// Block scaling
|
||||
Tensor sScaleAViewAsC = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_A.data()),
|
||||
Layout<
|
||||
Shape<Shape<Int<ScaleGranularityM>, Int<ScaleMsPerTile>>, cute::tuple_element_t<1, TileShape>, Int<DispatchPolicy::Stages>>,
|
||||
Stride<Stride<_0, _1>, _0, Int<ScaleMsPerTile>>
|
||||
>{}); // ((ScaleGranularityM,ScaleMsPerTile),n,k)
|
||||
Tensor sScaleBViewAsC = make_tensor(cute::make_smem_ptr(shared_tensors.smem_scale_B.data()),
|
||||
Layout<
|
||||
Shape<cute::tuple_element_t<0, TileShape>, Shape<Int<ScaleGranularityN>, Int<ScaleNsPerTile>>, Int<DispatchPolicy::Stages>>,
|
||||
Stride<_0, Stride<_0, _1>, Int<ScaleNsPerTile>>
|
||||
>{}); // (m,(ScaleGranularityN,ScaleNsPerTile),k)
|
||||
Tensor sSFA = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFA.data()), make_layout(
|
||||
make_shape(get<0>(shape(SmemLayoutSFA{})),
|
||||
get<1>(TileShape{}),
|
||||
make_shape(get<1>(shape(SmemLayoutSFA{})),
|
||||
get<2>(shape(SmemLayoutSFA{})))),
|
||||
make_stride(get<0>(stride(SmemLayoutSFA{})), _0{},
|
||||
make_stride(get<1>(stride(SmemLayoutSFA{})), get<2>(stride(SmemLayoutSFA{}))))
|
||||
)); // (BLK_M,BLK_N,(BLK_K,P))
|
||||
Tensor sSFB = make_tensor(cute::make_smem_ptr(shared_tensors.smem_SFB.data()), make_layout(
|
||||
make_shape(get<0>(TileShape{}),
|
||||
get<0>(shape(SmemLayoutSFB{})),
|
||||
make_shape(get<1>(shape(SmemLayoutSFB{})),
|
||||
get<2>(shape(SmemLayoutSFB{})))),
|
||||
make_stride(_0{},
|
||||
get<0>(stride(SmemLayoutSFB{})),
|
||||
make_stride(get<1>(stride(SmemLayoutSFB{})),
|
||||
get<2>(stride(SmemLayoutSFB{}))))
|
||||
)); // (BLK_M,BLK_N,(BLK_K,P))
|
||||
|
||||
//
|
||||
// Define C accumulators and A/B partitioning
|
||||
@@ -609,22 +637,22 @@ struct CollectiveMma<
|
||||
TiledMma tiled_mma;
|
||||
auto thread_mma = tiled_mma.get_slice(warp_group_thread_layout(warp_group_idx));
|
||||
|
||||
Tensor tCsScaleAViewAsC = tiled_mma.get_slice(thread_idx).partition_C(sScaleAViewAsC); // (MMA,MMA_M,MMA_N,PIPE), `thread_mma` above is correct when partitioning A and B, but it is not correct when partitioning C.
|
||||
Tensor tCsScaleBViewAsC = tiled_mma.get_slice(thread_idx).partition_C(sScaleBViewAsC); // (MMA,MMA_M,MMA_N,PIPE), `thread_mma` above is correct when partitioning A and B, but it is not correct when partitioning C.
|
||||
Tensor tCsSFA = tiled_mma.get_slice(thread_idx).partition_C(sSFA); // (MMA,MMA_M,MMA_N,(MMA_K,PIPE))
|
||||
Tensor tCsSFB = tiled_mma.get_slice(thread_idx).partition_C(sSFB); // (MMA,MMA_M,MMA_N,(MMA_K,PIPE))
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
// Allocate "fragments/descriptors"
|
||||
Tensor tCrA = thread_mma.make_fragment_A(tCsA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCrB = thread_mma.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
Tensor tCrA = thread_mma.make_fragment_A(tCsA); // (MMA,MMA_M,MMA_K,PIPE)
|
||||
Tensor tCrB = thread_mma.make_fragment_B(tCsB); // (MMA,MMA_N,MMA_K,PIPE)
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsA) == size<1>(accum)); // M
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsB) == size<2>(accum)); // N
|
||||
CUTE_STATIC_ASSERT_V(size<2>(tCsA) == size<2>(tCsB)); // K
|
||||
CUTE_STATIC_ASSERT_V(size<3>(tCsA) == size<3>(tCsB)); // PIPE
|
||||
CUTE_STATIC_ASSERT_V(Int<DispatchPolicy::Stages>{} == size<2>(sA)); // PIPE
|
||||
CUTE_STATIC_ASSERT_V(Int<DispatchPolicy::Stages>{} == size<2>(sB)); // PIPE
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsA) == size<1>(accum)); // M
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsB) == size<2>(accum)); // N
|
||||
CUTE_STATIC_ASSERT_V(size<2>(tCsA) == size<2>(tCsB)); // K
|
||||
CUTE_STATIC_ASSERT_V(size<3>(tCsA) == size<3>(tCsB)); // PIPE
|
||||
CUTE_STATIC_ASSERT_V(Int<DispatchPolicy::Stages>{} == size<2>(sA)); // PIPE
|
||||
CUTE_STATIC_ASSERT_V(Int<DispatchPolicy::Stages>{} == size<2>(sB)); // PIPE
|
||||
|
||||
//
|
||||
// PIPELINED MAIN LOOP
|
||||
@@ -636,8 +664,9 @@ struct CollectiveMma<
|
||||
PipelineState smem_pipe_release = smem_pipe_read;
|
||||
|
||||
// Per block scale values for operand A and B
|
||||
Tensor tCrScaleAViewAsC = make_tensor_like<ElementBlockScale>(tCsScaleAViewAsC(_, _, _, 0)); // (MMA,MMA_M,MMA_N)
|
||||
Tensor tCrScaleBViewAsC = make_tensor_like<ElementBlockScale>(tCsScaleBViewAsC(_, _, _, 0)); // (MMA,MMA_M,MMA_N)
|
||||
// Since scale factors always broadcast across MMA_K we slice that away
|
||||
Tensor tCrSFA = make_tensor_like<ElementBlockScale>(tCsSFA(_, _, _, _0{})); // (MMA,MMA_M,MMA_N)
|
||||
Tensor tCrSFB = make_tensor_like<ElementBlockScale>(tCsSFB(_, _, _, _0{})); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
// Prologue GMMAs
|
||||
int prologue_mma_count = min(K_PIPE_MMAS, k_tile_count);
|
||||
@@ -645,9 +674,6 @@ struct CollectiveMma<
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
|
||||
GmmaFP8Accumulation accumulation(accum, ScalePromotionInterval, size<2>(tCrA));
|
||||
warpgroup_fence_operand(accumulation());
|
||||
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);
|
||||
@@ -666,23 +692,24 @@ struct CollectiveMma<
|
||||
int read_stage = smem_pipe_read.index();
|
||||
|
||||
// Load per block scale values from shared memory to registers
|
||||
copy(tCsScaleAViewAsC(_, _, _, read_stage), tCrScaleAViewAsC);
|
||||
copy(tCsScaleBViewAsC(_, _, _, read_stage), tCrScaleBViewAsC);
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrScaleAViewAsC.data()[0] = tCrScaleAViewAsC.data()[0] * tCrScaleBViewAsC.data()[0];
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrScaleBViewAsC.data()[0];
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleAViewAsC); i++) {
|
||||
tCrScaleAViewAsC.data()[i] = tCrScaleAViewAsC.data()[i] * scale_b;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleBViewAsC); i++) {
|
||||
tCrScaleBViewAsC.data()[i] = tCrScaleBViewAsC.data()[i] * scale_a;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,19 +723,88 @@ struct CollectiveMma<
|
||||
}
|
||||
warpgroup_commit_batch();
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrScaleAViewAsC` and `tCrScaleBViewAsC`
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
++smem_pipe_read;
|
||||
}
|
||||
warpgroup_fence_operand(accumulation());
|
||||
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);
|
||||
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
if (accumulation.prepare_if_needed()) {
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Always zero out the accumulator for finest granularity
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
}
|
||||
|
||||
int read_stage = smem_pipe_read.index();
|
||||
|
||||
// Load per block scale values from shared memory to registers
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
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), accumulation());
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::One;
|
||||
}
|
||||
warpgroup_commit_batch();
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
++smem_pipe_read;
|
||||
@@ -732,26 +828,28 @@ struct CollectiveMma<
|
||||
int read_stage = smem_pipe_read.index();
|
||||
|
||||
// Load per block scale values from shared memory to registers (at most twice per block along M and/or N)
|
||||
copy(tCsScaleAViewAsC(_, _, _, read_stage), tCrScaleAViewAsC);
|
||||
copy(tCsScaleBViewAsC(_, _, _, read_stage), tCrScaleBViewAsC);
|
||||
copy(tCsSFA(_,_,_,make_coord(_0{},read_stage)), tCrSFA);
|
||||
copy(tCsSFB(_,_,_,make_coord(_0{},read_stage)), tCrSFB);
|
||||
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
tCrScaleAViewAsC.data()[0] = tCrScaleAViewAsC.data()[0] * tCrScaleBViewAsC.data()[0];
|
||||
tCrSFA(_0{}) = tCrSFA(_0{}) * tCrSFB(_0{});
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_b = tCrScaleBViewAsC.data()[0];
|
||||
ElementBlockScale scale_b = tCrSFB(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleAViewAsC); i++) {
|
||||
tCrScaleAViewAsC.data()[i] = tCrScaleAViewAsC.data()[i] * scale_b;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFA)); i++) {
|
||||
filter_zeros(tCrSFA)(i) = filter_zeros(tCrSFA)(i) * scale_b;
|
||||
}
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
ElementBlockScale scale_a = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_a = tCrSFA(_0{});
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < size(tCrScaleBViewAsC); i++) {
|
||||
tCrScaleBViewAsC.data()[i] = tCrScaleBViewAsC.data()[i] * scale_a;
|
||||
for (int i = 0; i < size(filter_zeros(tCrSFB)); i++) {
|
||||
filter_zeros(tCrSFB)(i) = filter_zeros(tCrSFB)(i) * scale_a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
if (accumulation.prepare_if_needed()) {
|
||||
tiled_mma.accumulate_ = GMMA::ScaleOut::Zero;
|
||||
@@ -777,19 +875,19 @@ struct CollectiveMma<
|
||||
warpgroup_wait<K_PIPE_MMAS>();
|
||||
warpgroup_fence_operand(accumulation());
|
||||
|
||||
// Block scale the accumulators with reg tensor `tCrScaleAViewAsC` and `tCrScaleBViewAsC`
|
||||
// Block scale the accumulators with reg tensor `tCrSFA` and `tCrSFB`
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
scale_if_needed(accumulation, tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
|
||||
pipeline.consumer_release(smem_pipe_release); // UNLOCK smem_pipe_release, done _computing_ on it
|
||||
@@ -802,17 +900,17 @@ struct CollectiveMma<
|
||||
if constexpr (ScalePromotionInterval != 4) {
|
||||
// residues only exists when granularity is not the finnest
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile == 1) {
|
||||
ElementBlockScale scale_ab = tCrScaleAViewAsC.data()[0];
|
||||
accumulation.scale_residue_if_needed(scale_ab);
|
||||
ElementBlockScale scale_ab = tCrSFA(_0{});
|
||||
scale_if_needed(accumulation, scale_ab);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile == 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleAViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile == 1 && ScaleNsPerTile > 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFB);
|
||||
}
|
||||
if constexpr (ScaleMsPerTile > 1 && ScaleNsPerTile > 1) {
|
||||
accumulation.scale_residue_if_needed(tCrScaleAViewAsC, tCrScaleBViewAsC);
|
||||
scale_if_needed(accumulation, tCrSFA, tCrSFB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +925,6 @@ struct CollectiveMma<
|
||||
k_tile_count -= prologue_mma_count;
|
||||
|
||||
smem_pipe_release.advance(k_tile_count);
|
||||
|
||||
// Wait on all GMMAs to complete
|
||||
warpgroup_wait<0>();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
#include "cute/layout.hpp"
|
||||
#include "cute/numeric/integral_constant.hpp" // cute::false_type
|
||||
#include "cute/arch/copy_sm100.hpp"
|
||||
#include "cute/atom/copy_traits_sm100.hpp"
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass::detail {
|
||||
@@ -72,7 +72,8 @@ namespace detail {
|
||||
|
||||
enum class KernelInputTransformType {
|
||||
FastF32,
|
||||
InterleavedComplexTF32
|
||||
InterleavedComplexTF32,
|
||||
MixedInput
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@@ -117,42 +118,9 @@ struct KernelPtrArrayTmaWarpSpecializedCooperative { };
|
||||
struct KernelPtrArrayTmaWarpSpecializedPingpong { };
|
||||
|
||||
// FP8 related policies (including Blocked Scaled Accumulation)
|
||||
template<
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` specifies scaling granularity along M/N, while zero-value
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` indicates that scaling granularity is
|
||||
// `size<0>(TileShape_MNK{})`/`size<1>(TileShape_MNK{})` along M/N.
|
||||
int ScaleGranularityM_ = 0,
|
||||
int ScaleGranularityN_ = 0,
|
||||
// `ScalePromotionInterval` specifies the interval to promote the accumulator for scaling
|
||||
// It is required to be a multiple of 4 and specified in terms of number of MMA instructions
|
||||
// in the reduction dimension. i.e for FP8 kernels, it is
|
||||
// ScalePromotionInterval * MMA_K = ScalePromotionInterval * 32 = 128 elements in K by default
|
||||
int ScalePromotionInterval_ = 4
|
||||
|
||||
>
|
||||
struct KernelTmaWarpSpecializedCooperativeFP8BlockScaledAccum: KernelTmaWarpSpecializedCooperative {
|
||||
constexpr static int ScaleGranularityM = ScaleGranularityM_;
|
||||
constexpr static int ScaleGranularityN = ScaleGranularityN_;
|
||||
constexpr static int ScalePromotionInterval = ScalePromotionInterval_;
|
||||
};
|
||||
|
||||
template<
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` specifies scaling granularity along M/N, while zero-value
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` indicates that scaling granularity is
|
||||
// `size<0>(TileShape_MNK{})`/`size<1>(TileShape_MNK{})` along M/N.
|
||||
int ScaleGranularityM_,
|
||||
int ScaleGranularityN_,
|
||||
// `ScalePromotionInterval` specifies the interval to promote the accumulator for scaling
|
||||
// It is required to be a multiple of 4 and specified in terms of number of MMA instructions
|
||||
// in the reduction dimension. i.e for FP8 kernels, it is
|
||||
// ScalePromotionInterval * MMA_K = ScalePromotionInterval * 32 = 128 elements in K by default
|
||||
int ScalePromotionInterval_ = 4
|
||||
>
|
||||
struct KernelPtrArrayTmaWarpSpecializedCooperativeFP8BlockScaledAccum: KernelPtrArrayTmaWarpSpecializedCooperative {
|
||||
constexpr static int ScaleGranularityM = ScaleGranularityM_;
|
||||
constexpr static int ScaleGranularityN = ScaleGranularityN_;
|
||||
constexpr static int ScalePromotionInterval = ScalePromotionInterval_;
|
||||
};
|
||||
struct KernelTmaWarpSpecializedCooperativeFP8BlockScaledAccum: KernelTmaWarpSpecializedCooperative { };
|
||||
struct KernelPtrArrayTmaWarpSpecializedCooperativeFP8BlockScaledAccum: KernelPtrArrayTmaWarpSpecializedCooperative { };
|
||||
struct KernelPtrArrayTmaWarpSpecializedPingpongFP8BlockScaledAccum: KernelPtrArrayTmaWarpSpecializedPingpong { };
|
||||
|
||||
// Policies to opt into mixed type GEMMs
|
||||
struct KernelTmaWarpSpecializedMixedInput : KernelTmaWarpSpecialized { };
|
||||
@@ -334,22 +302,12 @@ struct MainloopSm90TmaGmmaWarpSpecializedFP8
|
||||
template<
|
||||
int Stages_,
|
||||
class ClusterShape_ = Shape<_1,_1,_1>,
|
||||
class KernelSchedule = KernelTmaWarpSpecialized,
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` specifies scaling granularity along M/N, while zero-value
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` indicates that scaling granularity is
|
||||
// `size<0>(TileShape_MNK{})`/`size<1>(TileShape_MNK{})` along M/N.
|
||||
int ScaleGranularityM = 0,
|
||||
int ScaleGranularityN = 0,
|
||||
// `ScalePromotionInterval` specifies the interval to promote the accumulator for scaling
|
||||
// It is required to be a multiple of 4 and specified in terms of number of MMA instructions
|
||||
// in the reduction dimension. i.e for FP8 kernels, it is
|
||||
// ScalePromotionInterval * MMA_K = ScalePromotionInterval * 32 = 128 elements in K by default
|
||||
int ScalePromotionInterval = 4
|
||||
class KernelSchedule = KernelTmaWarpSpecialized
|
||||
>
|
||||
struct MainloopSm90TmaGmmaWarpSpecializedBlockScalingFP8
|
||||
: MainloopSm90TmaGmmaWarpSpecialized<Stages_, ClusterShape_, KernelSchedule> {
|
||||
static_assert(
|
||||
cute::is_same_v<KernelSchedule, KernelTmaWarpSpecializedCooperativeFP8BlockScaledAccum<ScaleGranularityM, ScaleGranularityN, ScalePromotionInterval>>,
|
||||
cute::is_same_v<KernelSchedule, KernelTmaWarpSpecializedCooperativeFP8BlockScaledAccum>,
|
||||
"KernelSchedule must be one of the warp specialized policies");
|
||||
};
|
||||
|
||||
@@ -431,18 +389,16 @@ struct MainloopSm90ArrayTmaGmmaWarpSpecializedMixedInput {
|
||||
template<
|
||||
int Stages_,
|
||||
class ClusterShape_ = Shape<_1,_1,_1>,
|
||||
class KernelSchedule = KernelPtrArrayTmaWarpSpecializedCooperative,
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` specifies scaling granularity along M/N, while zero-value
|
||||
// `ScaleGranularityM`/`ScaleGranularityN` indicates that scaling granularity is
|
||||
// `size<0>(TileShape_MNK{})`/`size<1>(TileShape_MNK{})` along M/N.
|
||||
int ScaleGranularityM = 0,
|
||||
int ScaleGranularityN = 0,
|
||||
int ScalePromotionInterval = 4
|
||||
class KernelSchedule = KernelPtrArrayTmaWarpSpecializedCooperative
|
||||
>
|
||||
struct MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling
|
||||
: MainloopSm90ArrayTmaGmmaWarpSpecialized<Stages_, ClusterShape_, KernelSchedule> {
|
||||
static_assert(
|
||||
cute::is_same_v<KernelSchedule, KernelPtrArrayTmaWarpSpecializedCooperativeFP8BlockScaledAccum<ScaleGranularityM, ScaleGranularityN>>,
|
||||
cute::is_any_of_v<
|
||||
KernelSchedule,
|
||||
KernelPtrArrayTmaWarpSpecializedCooperativeFP8BlockScaledAccum,
|
||||
KernelPtrArrayTmaWarpSpecializedPingpongFP8BlockScaledAccum
|
||||
>,
|
||||
"KernelSchedule must be one of the warp specialized policies");
|
||||
};
|
||||
|
||||
@@ -567,6 +523,28 @@ struct KernelTmaWarpSpecializedPingpongBlockScaledSm120 : KernelTmaWarpSpecializ
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
};
|
||||
|
||||
// SM120 dense Ptr-array kernel schedules
|
||||
template< int SchedulerPipelineStageCount_>
|
||||
struct KernelPtrArrayTmaWarpSpecializedCooperativeSm120 : KernelPtrArrayTmaWarpSpecializedCooperative {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
};
|
||||
|
||||
template< int SchedulerPipelineStageCount_>
|
||||
struct KernelPtrArrayTmaWarpSpecializedPingpongSm120 : KernelPtrArrayTmaWarpSpecializedPingpong {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
};
|
||||
|
||||
template< int SchedulerPipelineStageCount_>
|
||||
struct KernelPtrArrayTmaWarpSpecializedCooperativeBlockScaledSm120 : KernelPtrArrayTmaWarpSpecializedCooperative {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
};
|
||||
|
||||
template< int SchedulerPipelineStageCount_>
|
||||
struct KernelPtrArrayTmaWarpSpecializedPingpongBlockScaledSm120 : KernelPtrArrayTmaWarpSpecializedPingpong {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
};
|
||||
|
||||
// SM120 sparse kernel schedules
|
||||
template< int SchedulerPipelineStageCount_, bool isAsymmetric_>
|
||||
struct KernelTmaWarpSpecializedCooperativeSparseSm120 {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
@@ -578,6 +556,25 @@ struct KernelTmaWarpSpecializedCooperativeSparseBlockScaledSm120 {
|
||||
static constexpr int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
static constexpr bool isAsymmetric = isAsymmetric_;
|
||||
};
|
||||
|
||||
// Auxiliary Load Tag.
|
||||
|
||||
template<class Policy>
|
||||
struct IsAuxiliaryLoadNeeded : cute::false_type{};
|
||||
|
||||
template<
|
||||
int Stages,
|
||||
class ClusterShape,
|
||||
class KernelSchedule
|
||||
>
|
||||
struct IsAuxiliaryLoadNeeded<
|
||||
MainloopSm90ArrayTmaGmmaWarpSpecializedBlockScaling<
|
||||
Stages,
|
||||
ClusterShape,
|
||||
KernelSchedule
|
||||
>
|
||||
> : cute::true_type{};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
@@ -651,6 +648,14 @@ struct KernelTmaWarpSpecialized2SmFastFP32Sm100 final : KernelSchedule2Sm, Kerne
|
||||
struct KernelTmaWarpSpecialized1SmFastFP32SmemSm100 final : KernelSchedule1Sm, KernelTmaWarpSpecializedFastFP32SmemSm100 { };
|
||||
struct KernelTmaWarpSpecialized2SmFastFP32SmemSm100 final : KernelSchedule2Sm, KernelTmaWarpSpecializedFastFP32SmemSm100 { };
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SM100 Mixed Precision Input GEMM Dispatch Policies
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
struct KernelScheduleSm100MixedInputGemm : KernelScheduleSm100 {};
|
||||
struct KernelTmaWarpSpecializedMixedInputSmemSm100 : KernelScheduleSm100MixedInputGemm { };
|
||||
struct KernelTmaWarpSpecialized1SmMixedInputSm100 final : KernelSchedule1Sm, KernelScheduleSm100MixedInputGemm { };
|
||||
struct KernelTmaWarpSpecialized1SmMixedInputSmemSm100 final : KernelSchedule1Sm, KernelTmaWarpSpecializedMixedInputSmemSm100 { };
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SM100 Ptr-Array FastF32 (9xBF16) GEMM Dispatch Policies
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -997,16 +1002,30 @@ template<
|
||||
struct MainloopSm120TmaWarpSpecialized {
|
||||
constexpr static int Stages = Stages_;
|
||||
using ClusterShape = ClusterShape_;
|
||||
using KernelSchedule = KernelSchedule_;
|
||||
|
||||
using Schedule = KernelSchedule_;
|
||||
constexpr static int PipelineAsyncMmaStages = 0;
|
||||
using ArchTag = arch::Sm120;
|
||||
|
||||
using Schedule = cute::conditional_t<cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, KernelSchedule>,
|
||||
KernelTmaWarpSpecializedPingpongSm120<SchedulerPipelineStageCount_>,
|
||||
KernelTmaWarpSpecializedCooperativeSm120<SchedulerPipelineStageCount_>>;
|
||||
};
|
||||
|
||||
template<
|
||||
int Stages_,
|
||||
int SchedulerPipelineStageCount_,
|
||||
class ClusterShape_,
|
||||
class KernelSchedule_
|
||||
>
|
||||
struct MainloopSm120ArrayTmaWarpSpecialized {
|
||||
constexpr static int Stages = Stages_;
|
||||
using ClusterShape = ClusterShape_;
|
||||
using Schedule = KernelSchedule_;
|
||||
constexpr static int PipelineAsyncMmaStages = 0;
|
||||
using ArchTag = arch::Sm120;
|
||||
static_assert(
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, Schedule> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, Schedule>,
|
||||
"KernelSchedule must be one of the Ptr-Array or Grouped Gemm TMA Warp Specialized Cooperative or Pingpong policies");
|
||||
};
|
||||
|
||||
|
||||
template<
|
||||
int Stages_,
|
||||
int SchedulerPipelineStageCount_,
|
||||
@@ -1017,17 +1036,31 @@ struct MainloopSm120TmaWarpSpecializedBlockScaled {
|
||||
constexpr static int Stages = Stages_;
|
||||
constexpr static int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
using ClusterShape = ClusterShape_;
|
||||
using KernelSchedule = KernelSchedule_;
|
||||
|
||||
using Schedule = KernelSchedule_;
|
||||
constexpr static int PipelineAsyncMmaStages = 0;
|
||||
using ArchTag = arch::Sm120;
|
||||
|
||||
using Schedule = cute::conditional_t<cute::is_base_of_v<KernelTmaWarpSpecializedPingpong, KernelSchedule>,
|
||||
KernelTmaWarpSpecializedPingpongBlockScaledSm120<SchedulerPipelineStageCount_>,
|
||||
KernelTmaWarpSpecializedCooperativeBlockScaledSm120<SchedulerPipelineStageCount_>>;
|
||||
|
||||
};
|
||||
|
||||
template<
|
||||
int Stages_,
|
||||
int SchedulerPipelineStageCount_,
|
||||
class ClusterShape_,
|
||||
class KernelSchedule_
|
||||
>
|
||||
struct MainloopSm120ArrayTmaWarpSpecializedBlockScaled {
|
||||
constexpr static int Stages = Stages_;
|
||||
constexpr static int SchedulerPipelineStageCount = SchedulerPipelineStageCount_;
|
||||
using ClusterShape = ClusterShape_;
|
||||
constexpr static int PipelineAsyncMmaStages = 0;
|
||||
using Schedule = KernelSchedule_;
|
||||
using ArchTag = arch::Sm120;
|
||||
|
||||
static_assert(cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, Schedule> ||
|
||||
cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, Schedule>,
|
||||
"KernelSchedule must be one of the Ptr-Array or Grouped Gemm TMA Warp Specialized Cooperative or Pingpong policies.");
|
||||
};
|
||||
|
||||
|
||||
template<
|
||||
int StagesA_,
|
||||
int StagesB_,
|
||||
|
||||
@@ -176,10 +176,13 @@ public:
|
||||
using AccumulatorPipeline = cutlass::PipelineUmmaAsync<AccumulatorPipelineStageCount, AtomThrShapeMNK>;
|
||||
using AccumulatorPipelineState = typename AccumulatorPipeline::PipelineState;
|
||||
|
||||
using CLCPipeline = cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>;
|
||||
using CLCPipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>>;
|
||||
using CLCPipelineState = typename CLCPipeline::PipelineState;
|
||||
|
||||
using CLCThrottlePipeline = cutlass::PipelineAsync<SchedulerPipelineStageCount>;
|
||||
using CLCThrottlePipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>,
|
||||
cutlass::PipelineEmpty>;
|
||||
using CLCThrottlePipelineState = typename CLCThrottlePipeline::PipelineState;
|
||||
|
||||
using TmemAllocator = cute::conditional_t<cute::size(cute::shape<0>(typename TiledMma::ThrLayoutVMNK{})) == 1,
|
||||
@@ -331,7 +334,8 @@ public:
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
// Group GEMM currently only supports rank-3 problem shapes
|
||||
implementable &= (args.mode == GemmUniversalMode::kGrouped && rank(typename ProblemShape::UnderlyingProblemShape{}) == 3);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
implementable &= (args.mode == GemmUniversalMode::kArray && rank(typename ProblemShape::UnderlyingProblemShape{}) == 4);
|
||||
}
|
||||
if (!implementable) {
|
||||
@@ -486,7 +490,7 @@ public:
|
||||
auto cluster_shape = cutlass::detail::select_cluster_shape(ClusterShape{}, cute::cluster_shape());
|
||||
int cluster_size = size(cluster_shape);
|
||||
uint32_t cta_rank_in_cluster = cute::block_rank_in_cluster();
|
||||
bool is_first_cta_in_cluster = cta_rank_in_cluster == 0;
|
||||
bool is_first_cta_in_cluster = IsSchedDynamicPersistent ? (cta_rank_in_cluster == 0) : true;
|
||||
int cta_coord_v = cta_rank_in_cluster % size<0>(typename TiledMma::AtomThrID{});
|
||||
bool is_mma_leader_cta = cta_coord_v == 0;
|
||||
constexpr bool has_mma_peer_cta = size(AtomThrShapeMNK{}) == 2;
|
||||
@@ -555,22 +559,43 @@ public:
|
||||
|
||||
// CLC pipeline
|
||||
typename CLCPipeline::Params clc_pipeline_params;
|
||||
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::ProducerConsumer;
|
||||
clc_pipeline_params.role = IsSchedDynamicPersistent ?
|
||||
CLCPipeline::ThreadCategory::ProducerConsumer :
|
||||
CLCPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopLoadThreads + NumEpilogueThreads + NumMMAThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
|
||||
clc_pipeline_params.initializing_warp = 1;
|
||||
CLCPipeline clc_pipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopLoadThreads + NumEpilogueThreads + NumMMAThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.consumer_arv_count = NumMainloopLoadThreads + NumEpilogueThreads + NumMMAThreads;
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += NumEpilogueLoadThreads;
|
||||
}
|
||||
}
|
||||
// Now declare the pipeline outside the if constexpr
|
||||
CLCPipeline clc_pipeline = [&]() {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
}
|
||||
else {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params);
|
||||
}
|
||||
}();
|
||||
|
||||
// Mainloop-Epilogue pipeline
|
||||
typename AccumulatorPipeline::Params accumulator_pipeline_params;
|
||||
@@ -592,16 +617,18 @@ public:
|
||||
|
||||
// CLC throttle pipeline
|
||||
typename CLCThrottlePipeline::Params clc_throttle_pipeline_params;
|
||||
if (WarpCategory::MainloopLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
if (WarpCategory::MainloopLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
CLCThrottlePipeline clc_throttle_pipeline(shared_storage.pipelines.clc_throttle, clc_throttle_pipeline_params);
|
||||
CLCThrottlePipelineState clc_pipe_throttle_consumer_state;
|
||||
CLCThrottlePipelineState clc_pipe_throttle_producer_state = cutlass::make_producer_start_state<CLCThrottlePipeline>();
|
||||
@@ -666,7 +693,7 @@ public:
|
||||
|
||||
// TileID scheduler
|
||||
TileScheduler scheduler(&shared_storage.clc_response[0], params.scheduler, block_id_in_cluster);
|
||||
typename TileScheduler::WorkTileInfo work_tile_info = scheduler.initial_work_tile_info(cluster_shape);
|
||||
auto work_tile_info = scheduler.initial_work_tile_info(cluster_shape);
|
||||
auto cta_coord_mnkl = scheduler.work_tile_to_cta_coord(work_tile_info);
|
||||
|
||||
//
|
||||
@@ -797,6 +824,9 @@ public:
|
||||
}
|
||||
|
||||
else if (is_participant.sched) {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
// Signal the epilogue warps to proceed once the prologue is complete
|
||||
epilogue_throttle_barrier.arrive();
|
||||
|
||||
@@ -841,6 +871,16 @@ public:
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipe_producer_state);
|
||||
}
|
||||
else {
|
||||
do {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.advance_to_next_work(clc_pipeline, clc_pipe_producer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++clc_pipe_producer_state;
|
||||
}
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipe_producer_state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_participant.mma) {
|
||||
|
||||
+67
-24
@@ -170,10 +170,15 @@ public:
|
||||
|
||||
using LoadOrderBarrier = cutlass::OrderedSequenceBarrier<1,2>;
|
||||
|
||||
using CLCPipeline = cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>;
|
||||
using CLCPipelineState = cutlass::PipelineState<SchedulerPipelineStageCount>;
|
||||
|
||||
using CLCThrottlePipeline = cutlass::PipelineAsync<SchedulerPipelineStageCount>;
|
||||
using CLCPipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>>;
|
||||
using CLCPipelineState = typename CLCPipeline::PipelineState;
|
||||
|
||||
using CLCThrottlePipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>,
|
||||
cutlass::PipelineEmpty>;
|
||||
using CLCThrottlePipelineState = typename CLCThrottlePipeline::PipelineState;
|
||||
|
||||
using TmemAllocator = cute::conditional_t<cute::size(cute::shape<0>(typename TiledMma::ThrLayoutVMNK{})) == 1,
|
||||
@@ -428,7 +433,7 @@ public:
|
||||
int cta_rank_in_cluster = cute::block_rank_in_cluster();
|
||||
auto cluster_shape = cutlass::detail::select_cluster_shape(ClusterShape{}, cute::cluster_shape());
|
||||
int cluster_size = size(cluster_shape);
|
||||
bool is_first_cta_in_cluster = (cta_rank_in_cluster == 0);
|
||||
bool is_first_cta_in_cluster = IsSchedDynamicPersistent ? (cta_rank_in_cluster == 0) : true;
|
||||
bool is_mma_leader_cta = (cta_rank_in_cluster % size<0>(TiledMma{}) == 0);
|
||||
// Even if this variable is unused, shape_div still performs useful compile-time checks.
|
||||
[[maybe_unused]] auto mma_leader_ctas = size(shape_div(cluster_shape, AtomThrShapeMNK{}));
|
||||
@@ -552,38 +557,61 @@ public:
|
||||
// Operates Scheduling Warp <--> All Warps
|
||||
typename CLCPipeline::Params clc_pipeline_params;
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::ProducerConsumer;
|
||||
clc_pipeline_params.role = IsSchedDynamicPersistent ?
|
||||
CLCPipeline::ThreadCategory::ProducerConsumer :
|
||||
CLCPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopLoadThreads + NumEpilogueThreads +
|
||||
NumMMAThreads + NumTransformationThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
|
||||
clc_pipeline_params.initializing_warp = 1;
|
||||
CLCPipeline clc_pipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopLoadThreads + NumEpilogueThreads + NumMMAThreads +
|
||||
NumTransformationThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.consumer_arv_count = NumMainloopLoadThreads + NumEpilogueThreads + NumMMAThreads +
|
||||
NumTransformationThreads;
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += NumEpilogueLoadThreads;
|
||||
}
|
||||
}
|
||||
|
||||
CLCPipeline clc_pipeline = [&]() {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
}
|
||||
else {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params);
|
||||
}
|
||||
}();
|
||||
|
||||
CLCPipelineState clc_pipeline_consumer_state;
|
||||
CLCPipelineState clc_pipeline_producer_state = cutlass::make_producer_start_state<CLCPipeline>();
|
||||
|
||||
// CLC throttle pipeline
|
||||
typename CLCThrottlePipeline::Params clc_throttle_pipeline_params;
|
||||
if (WarpCategory::MainloopLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
if (WarpCategory::MainloopLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
CLCThrottlePipeline clc_throttle_pipeline(shared_storage.pipelines.clc_throttle, clc_throttle_pipeline_params);
|
||||
CLCThrottlePipelineState clc_pipe_throttle_consumer_state;
|
||||
CLCThrottlePipelineState clc_pipe_throttle_producer_state = cutlass::make_producer_start_state<CLCThrottlePipeline>();
|
||||
@@ -804,9 +832,14 @@ public:
|
||||
// Register reconfiguration
|
||||
arch::warpgroup_reg_dealloc<GenericRegisterRequirement>();
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
|
||||
// Signal the epilogue warps to proceed once the prologue is complete
|
||||
epilogue_throttle_barrier.arrive();
|
||||
|
||||
// Grouped GEMM uses static tile scheduler
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
// Whether a new CLC query must be performed.
|
||||
// See comment below where this variable is updated for a description of
|
||||
@@ -849,6 +882,16 @@ public:
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipeline_producer_state);
|
||||
}
|
||||
else {
|
||||
do {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.advance_to_next_work(clc_pipeline, clc_pipeline_producer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++clc_pipeline_producer_state;
|
||||
}
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipeline_producer_state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_participant.mma) {
|
||||
|
||||
+68
-27
@@ -179,10 +179,14 @@ public:
|
||||
using AccumulatorPipeline = typename CollectiveMainloop::AccumulatorPipeline;
|
||||
using AccumulatorPipelineState = typename AccumulatorPipeline::PipelineState;
|
||||
|
||||
using CLCPipeline = cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>;
|
||||
using CLCPipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineCLCFetchAsync<SchedulerPipelineStageCount, ClusterShape>,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>>;
|
||||
using CLCPipelineState = typename CLCPipeline::PipelineState;
|
||||
|
||||
using CLCThrottlePipeline = cutlass::PipelineAsync<SchedulerPipelineStageCount>;
|
||||
using CLCThrottlePipeline = cute::conditional_t<IsSchedDynamicPersistent,
|
||||
cutlass::PipelineAsync<SchedulerPipelineStageCount>,
|
||||
cutlass::PipelineEmpty>;
|
||||
using CLCThrottlePipelineState = typename CLCThrottlePipeline::PipelineState;
|
||||
|
||||
using TmemAllocator = cute::conditional_t<cute::size(cute::shape<0>(typename TiledMma::ThrLayoutVMNK{})) == 1,
|
||||
@@ -339,7 +343,8 @@ public:
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
// Group GEMM currently only supports rank-3 problem shapes
|
||||
implementable &= (args.mode == GemmUniversalMode::kGrouped && rank(typename ProblemShape::UnderlyingProblemShape{}) == 3);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
implementable &= (args.mode == GemmUniversalMode::kArray && rank(typename ProblemShape::UnderlyingProblemShape{}) == 4);
|
||||
}
|
||||
if (!implementable) {
|
||||
@@ -490,11 +495,14 @@ public:
|
||||
WarpCategory warp_category = [&] () CUTLASS_LAMBDA_FUNC_INLINE {
|
||||
if (warp_idx < static_cast<int>(WarpCategory::Epilogue)) {
|
||||
return WarpCategory(warp_idx);
|
||||
} else if (warp_idx < static_cast<int>(WarpCategory::MainloopSFLoad)) {
|
||||
}
|
||||
else if (warp_idx < static_cast<int>(WarpCategory::MainloopSFLoad)) {
|
||||
return WarpCategory::Epilogue;
|
||||
} else if (warp_idx == static_cast<int>(WarpCategory::MainloopSFLoad)) {
|
||||
}
|
||||
else if (warp_idx == static_cast<int>(WarpCategory::MainloopSFLoad)) {
|
||||
return WarpCategory::MainloopSFLoad;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return WarpCategory::Unused;
|
||||
}
|
||||
}();
|
||||
@@ -504,7 +512,7 @@ public:
|
||||
auto cluster_shape = cutlass::detail::select_cluster_shape(ClusterShape{}, cute::cluster_shape());
|
||||
int cluster_size = size(cluster_shape);
|
||||
uint32_t cta_rank_in_cluster = cute::block_rank_in_cluster();
|
||||
bool is_first_cta_in_cluster = cta_rank_in_cluster == 0;
|
||||
bool is_first_cta_in_cluster = IsSchedDynamicPersistent ? (cta_rank_in_cluster == 0) : true;
|
||||
int cta_coord_v = cta_rank_in_cluster % size<0>(typename TiledMma::AtomThrID{});
|
||||
bool is_mma_leader_cta = cta_coord_v == 0;
|
||||
constexpr bool has_mma_peer_cta = size(AtomThrShapeMNK{}) == 2;
|
||||
@@ -590,22 +598,43 @@ public:
|
||||
// CLC pipeline
|
||||
typename CLCPipeline::Params clc_pipeline_params;
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::ProducerConsumer;
|
||||
clc_pipeline_params.role = IsSchedDynamicPersistent ?
|
||||
CLCPipeline::ThreadCategory::ProducerConsumer :
|
||||
CLCPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.role = CLCPipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopABLoadThreads + NumEpilogueThreads +
|
||||
NumMainloopSFLoadThreads + NumMMAThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
|
||||
clc_pipeline_params.initializing_warp = 1;
|
||||
CLCPipeline clc_pipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
clc_pipeline_params.producer_arv_count = 1;
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
clc_pipeline_params.producer_blockid = 0;
|
||||
clc_pipeline_params.consumer_arv_count = NumSchedThreads + cluster_size *
|
||||
(NumMainloopABLoadThreads + NumEpilogueThreads +
|
||||
NumMainloopSFLoadThreads + NumMMAThreads);
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += cluster_size * NumEpilogueLoadThreads;
|
||||
}
|
||||
clc_pipeline_params.transaction_bytes = CLCResponseSize;
|
||||
}
|
||||
else {
|
||||
clc_pipeline_params.consumer_arv_count = NumMainloopABLoadThreads + NumEpilogueThreads + NumMMAThreads +
|
||||
NumMainloopSFLoadThreads;
|
||||
if (is_epi_load_needed) {
|
||||
clc_pipeline_params.consumer_arv_count += NumEpilogueLoadThreads;
|
||||
}
|
||||
}
|
||||
|
||||
CLCPipeline clc_pipeline = [&] () {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params, cluster_shape);
|
||||
}
|
||||
else {
|
||||
return CLCPipeline(shared_storage.pipelines.clc, clc_pipeline_params);
|
||||
}
|
||||
} ();
|
||||
|
||||
// Mainloop-Epilogue pipeline
|
||||
typename AccumulatorPipeline::Params accumulator_pipeline_params;
|
||||
@@ -625,16 +654,18 @@ public:
|
||||
|
||||
// CLC throttle pipeline
|
||||
typename CLCThrottlePipeline::Params clc_throttle_pipeline_params;
|
||||
if (WarpCategory::MainloopABLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
if (WarpCategory::MainloopABLoad == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Producer;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopABLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
}
|
||||
if (WarpCategory::Sched == warp_category) {
|
||||
clc_throttle_pipeline_params.role = CLCThrottlePipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
clc_throttle_pipeline_params.producer_arv_count = NumMainloopABLoadThreads;
|
||||
clc_throttle_pipeline_params.consumer_arv_count = NumSchedThreads;
|
||||
clc_throttle_pipeline_params.dst_blockid = 0;
|
||||
clc_throttle_pipeline_params.initializing_warp = 3;
|
||||
CLCThrottlePipeline clc_throttle_pipeline(shared_storage.pipelines.clc_throttle, clc_throttle_pipeline_params);
|
||||
CLCThrottlePipelineState clc_pipe_throttle_consumer_state;
|
||||
CLCThrottlePipelineState clc_pipe_throttle_producer_state = cutlass::make_producer_start_state<CLCThrottlePipeline>();
|
||||
@@ -944,6 +975,16 @@ public:
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipe_producer_state);
|
||||
}
|
||||
else {
|
||||
do {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.advance_to_next_work(clc_pipeline, clc_pipe_producer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++clc_pipe_producer_state;
|
||||
}
|
||||
} while (work_tile_info.is_valid());
|
||||
clc_pipeline.producer_tail(clc_pipe_producer_state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_participant.mma) {
|
||||
|
||||
@@ -649,6 +649,9 @@ public:
|
||||
}
|
||||
|
||||
else if (is_participant.sched) {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
// Whether a new CLC query must be performed.
|
||||
// See comment below where this variable is updated for a description of
|
||||
|
||||
@@ -717,7 +717,9 @@ public:
|
||||
// Register reconfiguration
|
||||
arch::warpgroup_reg_dealloc<GenericRegisterRequirement>();
|
||||
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
|
||||
// Signal the epilogue warps to proceed once the prologue is complete
|
||||
epilogue_throttle_barrier.arrive();
|
||||
@@ -1007,6 +1009,39 @@ public:
|
||||
// Advance the mm2accum pipe
|
||||
mma2accum_pipeline_consumer_state = mma2accum_pipeline_consumer_state_next;
|
||||
}
|
||||
else if constexpr (InputTransformType == cutlass::gemm::detail::KernelInputTransformType::MixedInput) {
|
||||
|
||||
mma2accum_pipeline.consumer_wait(mma2accum_pipeline_consumer_state);
|
||||
|
||||
// Accumulators
|
||||
Tensor accumulators = bulk_tmem(_,_,_,mma2accum_pipeline_consumer_state.index()); // ((MMA_TILE_M,MMA_TILE_N),MMA_M,MMA_N)
|
||||
|
||||
mma2accum_pipeline_consumer_state = scheduler.template fixup<IsComplex>(
|
||||
TiledMma{},
|
||||
work_tile_info,
|
||||
accumulators,
|
||||
mma2accum_pipeline,
|
||||
mma2accum_pipeline_consumer_state,
|
||||
typename CollectiveEpilogue::CopyOpT2R{}
|
||||
);
|
||||
|
||||
//
|
||||
// Epilogue and write to gD
|
||||
//
|
||||
if (scheduler.compute_epilogue(work_tile_info)) {
|
||||
auto [mma2accum_pipeline_state_next] = collective_epilogue(
|
||||
mma2accum_pipeline,
|
||||
mma2accum_pipeline_consumer_state,
|
||||
problem_shape_MNKL,
|
||||
CtaShape_MNK{},
|
||||
cta_coord_mnkl,
|
||||
accumulators,
|
||||
shared_storage.tensors.epilogue
|
||||
);
|
||||
// Advance the mma2accum pipe
|
||||
mma2accum_pipeline_consumer_state = mma2accum_pipeline_state_next;
|
||||
}
|
||||
}
|
||||
// Complex kernels use a collective epilogue
|
||||
else {
|
||||
mma2accum_pipeline.consumer_wait(mma2accum_pipeline_consumer_state);
|
||||
|
||||
@@ -769,6 +769,10 @@ public:
|
||||
// Register reconfiguration
|
||||
arch::warpgroup_reg_dealloc<GenericRegisterRequirement>();
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
|
||||
// Signal the epilogue warps to proceed once the prologue is complete
|
||||
epilogue_throttle_barrier.arrive();
|
||||
|
||||
|
||||
@@ -687,6 +687,9 @@ public:
|
||||
}
|
||||
|
||||
else if (is_participant.sched) {
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
// Whether a new CLC query must be performed.
|
||||
// See comment below where this variable is updated for a description of
|
||||
|
||||
@@ -95,23 +95,6 @@ public:
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
|
||||
Arguments() = default;
|
||||
Arguments(Arguments const&) = default;
|
||||
Arguments(Arguments&&) = default;
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments&
|
||||
operator=(Arguments const&) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments&
|
||||
operator=(Arguments &&) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
int max_swizzle_size = 0;
|
||||
RasterOrderOptions raster_order = RasterOrderOptions::Heuristic;
|
||||
};
|
||||
|
||||
@@ -54,18 +54,19 @@ namespace cutlass::gemm::kernel::detail {
|
||||
// If we had access to host-side problem shapes, one could to use it to figure out the grid shape
|
||||
// and thereafter use CLC query (which can then be linearized and mapped to an approriate tile coord).
|
||||
|
||||
template<class GroupProblemShape>
|
||||
template<class GroupProblemShape, int SchedulerPipelineStageCount>
|
||||
class PersistentTileSchedulerSm100Group {
|
||||
|
||||
public:
|
||||
using UnderlyingScheduler = PersistentTileSchedulerSm90Group<GroupProblemShape>;
|
||||
using UnderlyingScheduler = PersistentTileSchedulerSm90Group<GroupProblemShape, SchedulerPipelineStageCount>;
|
||||
using UnderlyingProblemShape = typename GroupProblemShape::UnderlyingProblemShape;
|
||||
using Params = PersistentTileSchedulerSm100GroupParams<UnderlyingProblemShape>;
|
||||
using WorkTileInfo = typename UnderlyingScheduler::WorkTileInfo;
|
||||
using Arguments = typename UnderlyingScheduler::Arguments;
|
||||
using RasterOrder = typename Params::RasterOrder;
|
||||
using RasterOrderOptions = typename Params::RasterOrderOptions;
|
||||
struct CLCResponse { uint32_t data[4]; };
|
||||
|
||||
using CLCResponse = WorkTileInfo;
|
||||
|
||||
static constexpr bool IsDynamicPersistent = UnderlyingScheduler::IsDynamicPersistent;
|
||||
|
||||
@@ -123,18 +124,19 @@ public:
|
||||
PersistentTileSchedulerSm100Group() { }
|
||||
|
||||
CUTLASS_DEVICE
|
||||
PersistentTileSchedulerSm100Group(CLCResponse* /* clc_response_ptr */, Params const& params)
|
||||
PersistentTileSchedulerSm100Group(CLCResponse* clc_response_ptr, Params const& params)
|
||||
: scheduler_params(params),
|
||||
scheduler_sm90(params.params_sm90_) { }
|
||||
scheduler_sm90(params.params_sm90_, clc_response_ptr) { }
|
||||
|
||||
CUTLASS_DEVICE
|
||||
PersistentTileSchedulerSm100Group(CLCResponse* /* clc_response_ptr */, Params const& params, dim3 /* block_id_in_cluster */)
|
||||
PersistentTileSchedulerSm100Group(CLCResponse* clc_response_ptr, Params const& params, dim3 /* block_id_in_cluster */)
|
||||
: scheduler_params(params),
|
||||
scheduler_sm90(params.params_sm90_) { }
|
||||
scheduler_sm90(params.params_sm90_, clc_response_ptr) { }
|
||||
|
||||
template <class ClusterShape>
|
||||
// Returns the initial work tile info that will be computed over
|
||||
template <typename ClusterShape>
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
auto
|
||||
initial_work_tile_info(ClusterShape cluster_shape) {
|
||||
return scheduler_sm90.initial_work_tile_info(cluster_shape);
|
||||
}
|
||||
@@ -194,6 +196,17 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CLCPipeline, typename CLCPipelineState>
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
advance_to_next_work(
|
||||
CLCPipeline& clc_pipeline,
|
||||
CLCPipelineState clc_pipe_producer_state,
|
||||
uint32_t advance_count = 1) {
|
||||
|
||||
return scheduler_sm90.advance_to_next_work(clc_pipeline, clc_pipe_producer_state, advance_count);
|
||||
}
|
||||
|
||||
//
|
||||
// K Tile API
|
||||
//
|
||||
@@ -282,10 +295,10 @@ public:
|
||||
auto
|
||||
fetch_next_work(
|
||||
WorkTileInfo work_tile_info,
|
||||
[[maybe_unused]] CLCPipeline& clc_pipeline,
|
||||
[[maybe_unused]] CLCPipelineState clc_pipe_consumer_state) {
|
||||
CLCPipeline& clc_pipeline,
|
||||
CLCPipelineState clc_pipe_consumer_state) {
|
||||
|
||||
return scheduler_sm90.fetch_next_work(work_tile_info);
|
||||
return scheduler_sm90.fetch_next_work(work_tile_info, clc_pipeline, clc_pipe_consumer_state);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -300,7 +313,6 @@ private:
|
||||
//
|
||||
// Storage
|
||||
//
|
||||
CLCResponse *clc_response_ptr_ = nullptr;
|
||||
Params scheduler_params;
|
||||
};
|
||||
|
||||
|
||||
+4
@@ -551,6 +551,10 @@ public:
|
||||
if (producer_warp_role == ProducerWarpRole::Warp1) {
|
||||
work_tile_info = scheduler.initial_work_tile_info(ClusterShape{});
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
cutlass::arch::wait_on_dependent_grids();
|
||||
}
|
||||
|
||||
if constexpr (IsSchedDynamicPersistent) {
|
||||
bool requires_clc_query = true;
|
||||
TileSchedulerPipelineState scheduler_pipe_producer_state = cutlass::make_producer_start_state<TileSchedulerPipeline>();
|
||||
|
||||
@@ -69,6 +69,17 @@ class GemmUniversal<
|
||||
cute::enable_if_t<cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedCooperative, typename CollectiveMainloop_::DispatchPolicy::Schedule>>
|
||||
>
|
||||
{
|
||||
// Get the type of the scheduler response.
|
||||
template<typename TileScheduler, typename = void>
|
||||
struct TileSchedulerResponseGetter {
|
||||
using Type = typename TileScheduler::CLCResponse;
|
||||
};
|
||||
|
||||
template<typename TileScheduler>
|
||||
struct TileSchedulerResponseGetter<TileScheduler, void_t<typename TileScheduler::SchedulerResponse>> {
|
||||
using Type = typename TileScheduler::SchedulerResponse;
|
||||
};
|
||||
|
||||
public:
|
||||
//
|
||||
// Type Aliases
|
||||
@@ -111,22 +122,42 @@ public:
|
||||
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<InternalStrideA, StrideA>;
|
||||
static constexpr uint32_t MinTensorMapWorkspaceAlignment = 64;
|
||||
|
||||
using TileScheduler = cute::conditional_t<IsGroupedGemmKernel,
|
||||
typename detail::TileSchedulerSelector<
|
||||
GroupScheduler, ArchTag,
|
||||
TileShape, ClusterShape,
|
||||
2, // Default unused parameter - SchedulerPipelineStageCount
|
||||
ProblemShape>::Scheduler,
|
||||
typename detail::TileSchedulerSelector<
|
||||
void, ArchTag, TileShape, ClusterShape>::Scheduler>;
|
||||
static_assert(
|
||||
cute::is_void_v<TileScheduler_>
|
||||
or (
|
||||
IsGroupedGemmKernel
|
||||
and cute::is_any_of_v<TileScheduler_, GroupScheduler>
|
||||
),
|
||||
"Ptr-Array Cooperative and Grouped Gemm Cooperative kernel only supports the default scheduler.");
|
||||
|
||||
using SchedulerTag = cute::conditional_t<
|
||||
cute::is_void_v<TileScheduler_>,
|
||||
cute::conditional_t<
|
||||
IsGroupedGemmKernel,
|
||||
GroupScheduler, // Special grouped gemm scheduler
|
||||
void // Default scheduler for non-grouped kernels
|
||||
>,
|
||||
TileScheduler_
|
||||
>;
|
||||
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
SchedulerTag,
|
||||
ArchTag,
|
||||
TileShape,
|
||||
ClusterShape,
|
||||
8, // SchedulerPipelineStageCount -- Grouped GEMM scheduler will benefit from a larger number of stages.
|
||||
cute::conditional_t<cute::is_same_v<SchedulerTag, void>, void, ProblemShape> // Use void for default scheduler.
|
||||
>::Scheduler;
|
||||
|
||||
static constexpr auto TileSchedulerStages = 8;
|
||||
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
using TileSchedulerParams = typename TileScheduler::Params;
|
||||
using TileSchedulerResponse = typename TileSchedulerResponseGetter<TileScheduler>::Type;
|
||||
|
||||
static constexpr uint32_t NumLoadWarpGroups = 1;
|
||||
static constexpr uint32_t NumMmaThreads = size(TiledMma{});
|
||||
@@ -134,6 +165,7 @@ public:
|
||||
static constexpr uint32_t MaxThreadsPerBlock = NumMmaThreads + (NumLoadWarpGroups * NumThreadsPerWarpGroup);
|
||||
static constexpr uint32_t MinBlocksPerMultiprocessor = 1;
|
||||
static constexpr uint32_t NumProducerThreads = CollectiveMainloop::NumProducerThreadEvents;
|
||||
static constexpr bool IsMainloopAuxiliaryLoadNeeded = IsAuxiliaryLoadNeeded<typename CollectiveMainloop::DispatchPolicy>::value;
|
||||
|
||||
/// Register requirement for Load and Math WGs
|
||||
static constexpr uint32_t LoadRegisterRequirement = 40;
|
||||
@@ -153,14 +185,18 @@ public:
|
||||
} tensors;
|
||||
|
||||
struct PipelineStorage : cute::aligned_struct<16, _1> {
|
||||
using TileSchedulerPipelineStorage = typename TileScheduler::PipelineStorage;
|
||||
using MainloopPipelineStorage = typename CollectiveMainloop::PipelineStorage;
|
||||
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
||||
|
||||
alignas(16) TileSchedulerPipelineStorage scheduler;
|
||||
alignas(16) MainloopPipelineStorage mainloop;
|
||||
alignas(16) EpiLoadPipelineStorage epi_load;
|
||||
alignas(16) typename LoadWarpOrderBarrier::SharedStorage load_order;
|
||||
} pipelines;
|
||||
|
||||
alignas(16) TileSchedulerResponse scheduler_response[TileSchedulerStages];
|
||||
|
||||
struct TensorMapStorage : cute::aligned_struct<128, _1> {
|
||||
using MainloopTensorMapStorage = typename CollectiveMainloop::TensorMapStorage;
|
||||
using EpilogueTensorMapStorage = typename CollectiveEpilogue::TensorMapStorage;
|
||||
@@ -271,7 +307,8 @@ public:
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
// Group GEMM currently only supports rank-3 problem shapes
|
||||
implementable &= (args.mode == GemmUniversalMode::kGrouped && rank(typename ProblemShape::UnderlyingProblemShape{}) == 3);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
implementable &= (args.mode == GemmUniversalMode::kArray && rank(typename ProblemShape::UnderlyingProblemShape{}) == 4);
|
||||
}
|
||||
if (!implementable) {
|
||||
@@ -375,9 +412,12 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
// 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(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL) || CUDA_ARCH_CONDITIONAL_OR_FAMILY(1200))
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the ISA is arch conditional.
|
||||
#if ! defined(ENABLE_SM90_KERNEL_LEVEL)
|
||||
printf("ERROR : Arch conditional MMA instruction used without targeting appropriate compute capability. Aborting.\n");
|
||||
#else
|
||||
|
||||
// Preconditions
|
||||
@@ -404,14 +444,27 @@ public:
|
||||
};
|
||||
enum class ProducerWarpRole {
|
||||
Mainloop = 0,
|
||||
Warp1 = 1,
|
||||
MainloopAux = 1,
|
||||
Epilogue = 2,
|
||||
Warp3 = 3
|
||||
Scheduler = 3
|
||||
};
|
||||
|
||||
// Kernel level shared memory storage
|
||||
SharedStorage& shared_storage = *reinterpret_cast<SharedStorage*>(smem_buf);
|
||||
|
||||
auto scheduler = [&] () {
|
||||
// Group scheduler requires a different constructor that takes a response ptr
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
return TileScheduler{params.scheduler, shared_storage.scheduler_response};
|
||||
}
|
||||
else {
|
||||
return TileScheduler{params.scheduler};
|
||||
}
|
||||
} ();
|
||||
// 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);
|
||||
|
||||
int thread_idx = int(threadIdx.x);
|
||||
int lane_idx = canonical_lane_idx();
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
@@ -426,10 +479,33 @@ public:
|
||||
|
||||
// Note: Tma Descriptor Prefetch (from either const or param) is not applicable here
|
||||
|
||||
// TileScheduler pipeline
|
||||
using TileSchedulerPipeline = typename TileScheduler::Pipeline;
|
||||
typename TileSchedulerPipeline::Params tile_scheduler_pipeline_params;
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
if (warp_group_role == WarpGroupRole::Producer
|
||||
&& producer_warp_role == ProducerWarpRole::Scheduler) {
|
||||
tile_scheduler_pipeline_params.role = TileSchedulerPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
else {
|
||||
tile_scheduler_pipeline_params.role = TileSchedulerPipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
tile_scheduler_pipeline_params.consumer_arv_count = NumMmaThreads
|
||||
+ NumThreadsPerWarp * (
|
||||
1 // Main DMA warp
|
||||
+ (collective_epilogue.is_producer_load_needed() ? 1 : 0) // Epilog DMA warp
|
||||
+ (IsMainloopAuxiliaryLoadNeeded ? 1 : 0) // Aux DMA warp
|
||||
);
|
||||
tile_scheduler_pipeline_params.producer_arv_count = 1;
|
||||
}
|
||||
TileSchedulerPipeline tile_scheduler_pipeline(shared_storage.pipelines.scheduler, tile_scheduler_pipeline_params);
|
||||
|
||||
// Mainloop Load pipeline
|
||||
using MainloopPipeline = typename CollectiveMainloop::MainloopPipeline;
|
||||
typename MainloopPipeline::Params mainloop_pipeline_params;
|
||||
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
if (warp_group_role == WarpGroupRole::Producer
|
||||
&& (producer_warp_role == ProducerWarpRole::Mainloop
|
||||
|| producer_warp_role == ProducerWarpRole::MainloopAux)) {
|
||||
mainloop_pipeline_params.role = MainloopPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
if (warp_group_role == WarpGroupRole::Consumer0 || warp_group_role == WarpGroupRole::Consumer1) {
|
||||
@@ -471,11 +547,13 @@ public:
|
||||
|
||||
// Initialize starting pipeline states for the collectives
|
||||
// Epilogue store pipe is producer-only (consumer is TMA unit, waits via scoreboarding)
|
||||
typename TileSchedulerPipeline::PipelineState tile_scheduler_pipe_consumer_state;
|
||||
typename CollectiveMainloop::PipelineState mainloop_pipe_consumer_state;
|
||||
typename CollectiveEpilogue::LoadPipelineState epi_load_pipe_consumer_state;
|
||||
|
||||
// For the DMA Load (producer) we start with an opposite phase
|
||||
// i.e., we skip all waits since we know that the buffer is indeed empty
|
||||
PipelineState tile_scheduler_pipe_producer_state = cutlass::make_producer_start_state<TileSchedulerPipeline>();
|
||||
PipelineState mainloop_pipe_producer_state = cutlass::make_producer_start_state<MainloopPipeline>();
|
||||
PipelineState epi_load_pipe_producer_state = cutlass::make_producer_start_state<EpiLoadPipeline>();
|
||||
PipelineState epi_store_pipe_producer_state = cutlass::make_producer_start_state<EpiStorePipeline>();
|
||||
@@ -499,16 +577,11 @@ public:
|
||||
const auto c_tile_count = CollectiveEpilogue::get_load_pipe_increment(blk_shape);
|
||||
const auto d_tile_count = CollectiveEpilogue::get_store_pipe_increment(blk_shape);
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
|
||||
// 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;
|
||||
@@ -533,8 +606,22 @@ public:
|
||||
if (warp_group_role == WarpGroupRole::Producer) {
|
||||
cutlass::arch::warpgroup_reg_dealloc<LoadRegisterRequirement>();
|
||||
|
||||
if (producer_warp_role == ProducerWarpRole::Scheduler) {
|
||||
// GroupScheduler requires a producer warp to iterate over the group infos and push
|
||||
// the work tile infos to the downstream pipelines.
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
do {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.advance_to_next_work(tile_scheduler_pipeline, tile_scheduler_pipe_producer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_producer_state;
|
||||
}
|
||||
} while (work_tile_info.is_valid());
|
||||
tile_scheduler_pipeline.producer_tail(tile_scheduler_pipe_producer_state);
|
||||
}
|
||||
}
|
||||
// Mainloop Producer Warp
|
||||
if (producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
else 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 const mock_l_coord = 0;
|
||||
int32_t const sm_idx = blockIdx.x + (blockIdx.y * gridDim.x);
|
||||
@@ -544,26 +631,28 @@ public:
|
||||
auto input_tensormaps = collective_mainloop.tensormaps_init(params.mainloop, shared_storage.tensormaps.mainloop, sm_count, sm_idx);
|
||||
|
||||
// Update tensormap for the initial batch for the CTA
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_mainloop.tensormaps_perform_update(
|
||||
shared_storage.tensormaps.mainloop,
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensormap fence release
|
||||
__syncwarp();
|
||||
// Entire warp must do this (i.e. it's aligned)
|
||||
collective_mainloop.tensormaps_cp_fence_release(shared_storage.tensormaps.mainloop, input_tensormaps);
|
||||
}
|
||||
collective_mainloop.tensormaps_perform_update(
|
||||
shared_storage.tensormaps.mainloop,
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensormap fence release
|
||||
__syncwarp();
|
||||
// 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()) {
|
||||
do {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(
|
||||
work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -605,8 +694,11 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
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) {
|
||||
@@ -633,12 +725,73 @@ public:
|
||||
}
|
||||
// Advance the producer state for the last remaining stage that was being waited for above
|
||||
mainloop_pipe_producer_state.advance(1);
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // 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
|
||||
else if (producer_warp_role == ProducerWarpRole::MainloopAux) {
|
||||
if constexpr (IsMainloopAuxiliaryLoadNeeded) {
|
||||
int32_t curr_batch = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl)); // Usually just returns work_tile_info.L_idx;
|
||||
int32_t const mock_l_coord = 0;
|
||||
|
||||
bool did_batch_change = true;
|
||||
do {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 blk_coord = make_coord(m_coord, n_coord, _, mock_l_coord);
|
||||
|
||||
// Get the number of K tiles to compute for this work as well as the starting K tile offset of the work.
|
||||
auto work_k_tile_count = TileScheduler::get_work_k_tile_count(work_tile_info, problem_shape_MNKL, blk_shape);
|
||||
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));
|
||||
|
||||
if (did_batch_change) {
|
||||
load_inputs = collective_mainloop.tensors_perform_update(load_inputs, params.mainloop, problem_shape_MNKL, curr_batch);
|
||||
}
|
||||
|
||||
collective_mainloop.load_auxiliary(
|
||||
params.mainloop,
|
||||
mainloop_pipeline,
|
||||
mainloop_pipe_producer_state,
|
||||
load_inputs,
|
||||
blk_coord,
|
||||
k_tile_iter, work_k_tile_count,
|
||||
lane_idx,
|
||||
block_rank_in_cluster,
|
||||
shared_storage.tensors.mainloop
|
||||
);
|
||||
// Update starting pipeline state for the next tile
|
||||
// Wait for the last TMA stage to complete loading, before issuing tensormap updates
|
||||
mainloop_pipe_producer_state.advance(work_k_tile_count);
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
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(curr_batch), 1);
|
||||
}
|
||||
}
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
@@ -649,28 +802,26 @@ public:
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = true;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
0
|
||||
);
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
0
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, 0);
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, 0);
|
||||
|
||||
load_order_barrier.wait();
|
||||
}
|
||||
load_order_barrier.wait();
|
||||
|
||||
while (work_tile_info.is_valid()) {
|
||||
do {
|
||||
int32_t curr_batch = work_tile_info.L_idx;
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
|
||||
if (TileScheduler::compute_epilogue(work_tile_info, params.scheduler)) {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
@@ -703,8 +854,10 @@ public:
|
||||
wait
|
||||
);
|
||||
}
|
||||
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
@@ -729,7 +882,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
collective_epilogue.load_tail(epi_load_pipeline, epi_load_pipe_producer_state);
|
||||
@@ -752,27 +905,24 @@ public:
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = false;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
if (warp_idx_in_warp_group == 0) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
consumer_warp_group_idx
|
||||
);
|
||||
|
||||
if (warp_idx_in_warp_group == 0) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
consumer_warp_group_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue,
|
||||
epi_store_tensormap,
|
||||
consumer_warp_group_idx);
|
||||
}
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue,
|
||||
epi_store_tensormap,
|
||||
consumer_warp_group_idx);
|
||||
}
|
||||
|
||||
while (work_tile_info.is_valid()) {
|
||||
do {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), 1);
|
||||
}
|
||||
@@ -791,9 +941,6 @@ public:
|
||||
// MSVC CTAD breaks if we say "Tensor" here, so we use "auto" instead.
|
||||
auto accumulators = partition_fragment_C(tiled_mma, take<0,2>(blk_shape)); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
static_assert(cute::is_any_of_v<TileScheduler,
|
||||
detail::PersistentTileSchedulerSm90Group<ProblemShape>,
|
||||
detail::PersistentTileSchedulerSm90>);
|
||||
if (TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
|
||||
collective_mainloop.mma(
|
||||
@@ -851,8 +998,11 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
@@ -877,7 +1027,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
|
||||
// Cooperative only needs TMA to complete at the very end of the kernel
|
||||
if (do_store_tail) {
|
||||
|
||||
@@ -69,6 +69,18 @@ class GemmUniversal<
|
||||
cute::enable_if_t<cute::is_base_of_v<KernelPtrArrayTmaWarpSpecializedPingpong, typename CollectiveMainloop_::DispatchPolicy::Schedule>>
|
||||
>
|
||||
{
|
||||
|
||||
// Get the type of the scheduler response.
|
||||
template<typename TileScheduler, typename = void>
|
||||
struct TileSchedulerResponseGetter {
|
||||
using Type = typename TileScheduler::CLCResponse;
|
||||
};
|
||||
|
||||
template<typename TileScheduler>
|
||||
struct TileSchedulerResponseGetter<TileScheduler, void_t<typename TileScheduler::SchedulerResponse>> {
|
||||
using Type = typename TileScheduler::SchedulerResponse;
|
||||
};
|
||||
|
||||
public:
|
||||
//
|
||||
// Type Aliases
|
||||
@@ -111,28 +123,50 @@ public:
|
||||
using EpilogueParams = typename CollectiveEpilogue::Params;
|
||||
|
||||
static_assert(ArchTag::kMinComputeCapability >= 90);
|
||||
static_assert(cute::is_void_v<TileScheduler_>,
|
||||
"Ptr-Array Pingpong and Grouped Gemm Pingpong kernel only supports the default scheduler.");
|
||||
|
||||
static constexpr bool IsGroupedGemmKernel = !cute::is_same_v<InternalStrideA, StrideA>;
|
||||
static constexpr uint32_t MinTensorMapWorkspaceAlignment = 64;
|
||||
|
||||
using TileScheduler = cute::conditional_t<IsGroupedGemmKernel,
|
||||
typename detail::TileSchedulerSelector<
|
||||
GroupScheduler, ArchTag,
|
||||
TileShape, ClusterShape,
|
||||
2, // Default unused parameter - SchedulerPipelineStageCount
|
||||
ProblemShape>::Scheduler,
|
||||
typename detail::TileSchedulerSelector<
|
||||
void, ArchTag, TileShape, ClusterShape>::Scheduler>;
|
||||
static_assert(
|
||||
cute::is_void_v<TileScheduler_>
|
||||
or (
|
||||
IsGroupedGemmKernel
|
||||
and cute::is_any_of_v<TileScheduler_, GroupScheduler>
|
||||
),
|
||||
"Ptr-Array Pingpong and Grouped Gemm Pingpong kernel only supports the default scheduler.");
|
||||
|
||||
using SchedulerTag = cute::conditional_t<
|
||||
cute::is_void_v<TileScheduler_>,
|
||||
cute::conditional_t<
|
||||
IsGroupedGemmKernel,
|
||||
GroupScheduler, // Special grouped gemm scheduler
|
||||
void // Default scheduler for non-grouped kernels
|
||||
>,
|
||||
TileScheduler_
|
||||
>;
|
||||
|
||||
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
SchedulerTag,
|
||||
ArchTag,
|
||||
TileShape,
|
||||
ClusterShape,
|
||||
8, // SchedulerPipelineStageCount -- Grouped GEMM scheduler will benefit from a larger number of stages.
|
||||
cute::conditional_t<cute::is_same_v<SchedulerTag, void>, void, ProblemShape> // Use void for default scheduler.
|
||||
>::Scheduler;
|
||||
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
using TileSchedulerParams = typename TileScheduler::Params;
|
||||
using TileSchedulerResponse = typename TileSchedulerResponseGetter<TileScheduler>::Type;
|
||||
|
||||
static constexpr auto TileSchedulerStages = 8;
|
||||
|
||||
static constexpr uint32_t NumLoadWarpGroups = 1;
|
||||
static constexpr uint32_t NumMmaWarpGroups = 2;
|
||||
static constexpr uint32_t MaxThreadsPerBlock = CUTE_STATIC_V(size(TiledMma{})) + (NumMmaWarpGroups * NumThreadsPerWarpGroup);
|
||||
static constexpr uint32_t MinBlocksPerMultiprocessor = 1;
|
||||
static constexpr uint32_t NumProducerThreads = CollectiveMainloop::NumProducerThreadEvents;
|
||||
static constexpr bool IsMainloopAuxiliaryLoadNeeded = IsAuxiliaryLoadNeeded<typename CollectiveMainloop::DispatchPolicy>::value;
|
||||
|
||||
/// Register requirement for Load and Math WGs
|
||||
static constexpr uint32_t LoadRegisterRequirement = 40;
|
||||
@@ -159,16 +193,20 @@ public:
|
||||
} tensors;
|
||||
|
||||
struct PipelineStorage : cute::aligned_struct<16, _1> {
|
||||
using TileSchedulerPipelineStorage = typename TileScheduler::PipelineStorage;
|
||||
using MainloopPipelineStorage = typename CollectiveMainloop::PipelineStorage;
|
||||
using EpiLoadPipelineStorage = typename CollectiveEpilogue::PipelineStorage;
|
||||
using MathWarpGroupOrderBarrierStorage = MathWarpGroupOrderBarrierSharedStorage;
|
||||
|
||||
alignas(16) TileSchedulerPipelineStorage scheduler;
|
||||
alignas(16) MainloopPipelineStorage mainloop;
|
||||
alignas(16) EpiLoadPipelineStorage epi_load;
|
||||
alignas(16) typename LoadWarpOrderBarrier::SharedStorage load_order;
|
||||
alignas(16) MathWarpGroupOrderBarrierStorage math_wg_order;
|
||||
} pipelines;
|
||||
|
||||
alignas(16) TileSchedulerResponse scheduler_response[TileSchedulerStages];
|
||||
|
||||
struct TensorMapStorage : cute::aligned_struct<128, _1> {
|
||||
using MainloopTensorMapStorage = typename CollectiveMainloop::TensorMapStorage;
|
||||
using EpilogueTensorMapStorage = typename CollectiveEpilogue::TensorMapStorage;
|
||||
@@ -283,7 +321,8 @@ public:
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
// Group GEMM currently only supports rank-3 problem shapes
|
||||
implementable &= (args.mode == GemmUniversalMode::kGrouped && rank(typename ProblemShape::UnderlyingProblemShape{}) == 3);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
implementable &= (args.mode == GemmUniversalMode::kArray && rank(typename ProblemShape::UnderlyingProblemShape{}) == 4);
|
||||
}
|
||||
if (!implementable) {
|
||||
@@ -386,9 +425,12 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
// 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(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL) || CUDA_ARCH_CONDITIONAL_OR_FAMILY(1200))
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the ISA is arch conditional.
|
||||
#if ! defined(ENABLE_SM90_KERNEL_LEVEL)
|
||||
printf("ERROR : Arch conditional MMA instruction used without targeting appropriate compute capability. Aborting.\n");
|
||||
#else
|
||||
|
||||
// Preconditions
|
||||
@@ -412,14 +454,28 @@ public:
|
||||
};
|
||||
enum class ProducerWarpRole {
|
||||
Mainloop = 0,
|
||||
Warp1 = 1,
|
||||
MainloopAux = 1,
|
||||
Epilogue = 2,
|
||||
Warp3 = 3
|
||||
Scheduler = 3
|
||||
};
|
||||
|
||||
// Kernel level shared memory storage
|
||||
SharedStorage& shared_storage = *reinterpret_cast<SharedStorage*>(smem_buf);
|
||||
|
||||
auto scheduler = [&] () {
|
||||
// Group scheduler requires a different constructor that takes a response ptr
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
return TileScheduler{params.scheduler, shared_storage.scheduler_response};
|
||||
}
|
||||
else {
|
||||
return TileScheduler{params.scheduler};
|
||||
}
|
||||
} ();
|
||||
|
||||
// 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);
|
||||
|
||||
int thread_idx = int(threadIdx.x);
|
||||
int lane_idx = canonical_lane_idx();
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
@@ -434,10 +490,32 @@ public:
|
||||
|
||||
// Note: Tma Descriptor Prefetch (from either const or param) is not applicable here
|
||||
|
||||
// TileScheduler pipeline
|
||||
using TileSchedulerPipeline = typename TileScheduler::Pipeline;
|
||||
typename TileSchedulerPipeline::Params tile_scheduler_pipeline_params;
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
if (warp_group_role == WarpGroupRole::Producer
|
||||
&& producer_warp_role == ProducerWarpRole::Scheduler) {
|
||||
tile_scheduler_pipeline_params.role = TileSchedulerPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
else {
|
||||
tile_scheduler_pipeline_params.role = TileSchedulerPipeline::ThreadCategory::Consumer;
|
||||
}
|
||||
tile_scheduler_pipeline_params.consumer_arv_count = NumThreadsPerWarpGroup * NumMmaWarpGroups // 1 MATH WG
|
||||
+ NumThreadsPerWarp * (
|
||||
1 // Main DMA warp
|
||||
+ (collective_epilogue.is_producer_load_needed() ? 1 : 0) // Epilog DMA warp
|
||||
+ (IsMainloopAuxiliaryLoadNeeded ? 1 : 0) // Aux DMA warp
|
||||
);
|
||||
tile_scheduler_pipeline_params.producer_arv_count = 1;
|
||||
}
|
||||
TileSchedulerPipeline tile_scheduler_pipeline(shared_storage.pipelines.scheduler, tile_scheduler_pipeline_params);
|
||||
// Mainloop Load pipeline
|
||||
using MainloopPipeline = typename CollectiveMainloop::MainloopPipeline;
|
||||
typename MainloopPipeline::Params mainloop_pipeline_params;
|
||||
if (warp_group_role == WarpGroupRole::Producer && producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
if (warp_group_role == WarpGroupRole::Producer
|
||||
&& (producer_warp_role == ProducerWarpRole::Mainloop
|
||||
|| producer_warp_role == ProducerWarpRole::MainloopAux)) {
|
||||
mainloop_pipeline_params.role = MainloopPipeline::ThreadCategory::Producer;
|
||||
}
|
||||
if (warp_group_role == WarpGroupRole::Consumer0 || warp_group_role == WarpGroupRole::Consumer1) {
|
||||
@@ -485,11 +563,13 @@ public:
|
||||
|
||||
// Initialize starting pipeline states for the collectives
|
||||
// Epilogue store pipe is producer-only (consumer is TMA unit, waits via scoreboarding)
|
||||
typename TileSchedulerPipeline::PipelineState tile_scheduler_pipe_consumer_state;
|
||||
typename CollectiveMainloop::PipelineState mainloop_pipe_consumer_state;
|
||||
typename CollectiveEpilogue::LoadPipelineState epi_load_pipe_consumer_state;
|
||||
|
||||
// For the DMA Load (producer) we start with an opposite phase
|
||||
// i.e., we skip all waits since we know that the buffer is indeed empty
|
||||
PipelineState tile_scheduler_pipe_producer_state = cutlass::make_producer_start_state<TileSchedulerPipeline>();
|
||||
PipelineState mainloop_pipe_producer_state = cutlass::make_producer_start_state<MainloopPipeline>();
|
||||
PipelineState epi_load_pipe_producer_state = cutlass::make_producer_start_state<EpiLoadPipeline>();
|
||||
PipelineState epi_store_pipe_producer_state = cutlass::make_producer_start_state<EpiStorePipeline>();
|
||||
@@ -513,16 +593,11 @@ public:
|
||||
const auto c_tile_count = CollectiveEpilogue::get_load_pipe_increment(blk_shape);
|
||||
const auto d_tile_count = CollectiveEpilogue::get_store_pipe_increment(blk_shape);
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
|
||||
// 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;
|
||||
@@ -531,16 +606,21 @@ public:
|
||||
// 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), 1);
|
||||
|
||||
if (warp_group_role == WarpGroupRole::Consumer1) {
|
||||
// Consumer1 is not on the critical path at prologue.
|
||||
if (warp_group_role == WarpGroupRole::Consumer1) [[unlikely]] {
|
||||
// Advance 2nd Math WG to the next work tile for the startup
|
||||
const auto k_tile_count = TileScheduler::get_work_k_tile_count(work_tile_info, problem_shape_MNKL, blk_shape);
|
||||
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (!work_tile_info.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
|
||||
// Advance 2nd Math WG pipeline states to the end of 1st Math WG
|
||||
mainloop_pipe_consumer_state.advance(k_tile_count);
|
||||
epi_load_pipe_consumer_state.advance(c_tile_count);
|
||||
@@ -565,8 +645,20 @@ public:
|
||||
if (warp_group_role == WarpGroupRole::Producer) {
|
||||
cutlass::arch::warpgroup_reg_dealloc<LoadRegisterRequirement>();
|
||||
|
||||
if (producer_warp_role == ProducerWarpRole::Scheduler) {
|
||||
if constexpr (cute::is_same_v<SchedulerTag, GroupScheduler>) {
|
||||
do {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.advance_to_next_work(tile_scheduler_pipeline, tile_scheduler_pipe_producer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_producer_state;
|
||||
}
|
||||
} while (work_tile_info.is_valid());
|
||||
tile_scheduler_pipeline.producer_tail(tile_scheduler_pipe_producer_state);
|
||||
}
|
||||
}
|
||||
// Mainloop Producer Warp
|
||||
if (producer_warp_role == ProducerWarpRole::Mainloop) {
|
||||
else 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 const mock_l_coord = 0;
|
||||
int32_t const sm_idx = blockIdx.x + (blockIdx.y * gridDim.x);
|
||||
@@ -576,26 +668,27 @@ public:
|
||||
auto input_tensormaps = collective_mainloop.tensormaps_init(params.mainloop, shared_storage.tensormaps.mainloop, sm_count, sm_idx);
|
||||
|
||||
// Update tensormap for the initial batch for the CTA
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_mainloop.tensormaps_perform_update(
|
||||
shared_storage.tensormaps.mainloop,
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensormap fence release
|
||||
__syncwarp();
|
||||
// Entire warp must do this (i.e. it's aligned)
|
||||
collective_mainloop.tensormaps_cp_fence_release(shared_storage.tensormaps.mainloop, input_tensormaps);
|
||||
}
|
||||
collective_mainloop.tensormaps_perform_update(
|
||||
shared_storage.tensormaps.mainloop,
|
||||
params.mainloop,
|
||||
input_tensormaps,
|
||||
problem_shape_MNKL,
|
||||
curr_batch
|
||||
);
|
||||
// Ensure warp is converged before issuing tensormap fence release
|
||||
__syncwarp();
|
||||
// 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()) {
|
||||
do {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -637,8 +730,11 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
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) {
|
||||
@@ -665,12 +761,73 @@ public:
|
||||
}
|
||||
// Advance the producer state for the last remaining stage that was being waited for above
|
||||
mainloop_pipe_producer_state.advance(1);
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // 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
|
||||
else if (producer_warp_role == ProducerWarpRole::MainloopAux) {
|
||||
if constexpr (IsMainloopAuxiliaryLoadNeeded) {
|
||||
int32_t curr_batch = idx2crd(work_tile_info.L_idx, shape<4>(gB_nkl)); // Usually just returns work_tile_info.L_idx;
|
||||
int32_t const mock_l_coord = 0;
|
||||
|
||||
bool did_batch_change = true;
|
||||
do {
|
||||
if (!TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 blk_coord = make_coord(m_coord, n_coord, _, mock_l_coord);
|
||||
|
||||
// Get the number of K tiles to compute for this work as well as the starting K tile offset of the work.
|
||||
auto work_k_tile_count = TileScheduler::get_work_k_tile_count(work_tile_info, problem_shape_MNKL, blk_shape);
|
||||
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));
|
||||
|
||||
if (did_batch_change) {
|
||||
load_inputs = collective_mainloop.tensors_perform_update(load_inputs, params.mainloop, problem_shape_MNKL, curr_batch);
|
||||
}
|
||||
|
||||
collective_mainloop.load_auxiliary(
|
||||
params.mainloop,
|
||||
mainloop_pipeline,
|
||||
mainloop_pipe_producer_state,
|
||||
load_inputs,
|
||||
blk_coord,
|
||||
k_tile_iter, work_k_tile_count,
|
||||
lane_idx,
|
||||
block_rank_in_cluster,
|
||||
shared_storage.tensors.mainloop
|
||||
);
|
||||
|
||||
// Update starting pipeline state for the next tile
|
||||
mainloop_pipe_producer_state.advance(work_k_tile_count);
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
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(curr_batch), 1);
|
||||
}
|
||||
}
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
} // End of auxiliary load needed check
|
||||
} // Mainloop Auxiliary Load Producer Warp End
|
||||
// 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);
|
||||
@@ -681,28 +838,26 @@ public:
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = true;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
0
|
||||
);
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_load_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
0
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, 0);
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue, epi_load_tensormap, 0);
|
||||
|
||||
load_order_barrier.wait();
|
||||
}
|
||||
load_order_barrier.wait();
|
||||
|
||||
while (work_tile_info.is_valid()) {
|
||||
do {
|
||||
int32_t curr_batch = work_tile_info.L_idx;
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
|
||||
if (TileScheduler::compute_epilogue(work_tile_info, params.scheduler)) {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
@@ -737,6 +892,9 @@ public:
|
||||
}
|
||||
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
|
||||
if (work_tile_info.is_valid() && did_batch_change) {
|
||||
@@ -761,7 +919,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
|
||||
// Make sure all Consumer Warp Groups have been waited upon
|
||||
collective_epilogue.load_tail(epi_load_pipeline, epi_load_pipe_producer_state);
|
||||
@@ -784,27 +942,24 @@ public:
|
||||
bool did_batch_change = true;
|
||||
constexpr bool IsEpiLoad = false;
|
||||
|
||||
if (work_tile_info.is_valid()) {
|
||||
if (warp_idx_in_warp_group == 0) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
consumer_warp_group_idx
|
||||
);
|
||||
|
||||
if (warp_idx_in_warp_group == 0) {
|
||||
collective_epilogue.template tensormaps_perform_update<IsEpiLoad>(
|
||||
shared_storage.tensormaps.epilogue,
|
||||
params.epilogue,
|
||||
epi_store_tensormap,
|
||||
problem_shape_MNKL,
|
||||
work_tile_info.L_idx,
|
||||
consumer_warp_group_idx
|
||||
);
|
||||
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue,
|
||||
epi_store_tensormap,
|
||||
consumer_warp_group_idx);
|
||||
}
|
||||
// Converge before issuing tensormap fence release since fence is aligned
|
||||
__syncwarp();
|
||||
collective_epilogue.template tensormaps_cp_fence_release<IsEpiLoad>(shared_storage.tensormaps.epilogue,
|
||||
epi_store_tensormap,
|
||||
consumer_warp_group_idx);
|
||||
}
|
||||
|
||||
while (work_tile_info.is_valid()) {
|
||||
do {
|
||||
if constexpr (IsGroupedGemmKernel) {
|
||||
problem_shape_MNKL = append<4>(params.problem_shape.get_problem_shape(work_tile_info.L_idx), 1);
|
||||
}
|
||||
@@ -823,9 +978,6 @@ public:
|
||||
// MSVC CTAD breaks if we say "Tensor" here, so we use "auto" instead.
|
||||
auto accumulators = partition_fragment_C(tiled_mma, take<0,2>(blk_shape)); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
static_assert(cute::is_any_of_v<TileScheduler,
|
||||
detail::PersistentTileSchedulerSm90Group<ProblemShape>,
|
||||
detail::PersistentTileSchedulerSm90>);
|
||||
if (TileScheduler::valid_warpgroup_in_work_tile(work_tile_info)) {
|
||||
|
||||
math_wg_order_barrier.wait();
|
||||
@@ -889,8 +1041,11 @@ public:
|
||||
}
|
||||
|
||||
// Get next work tile
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
|
||||
// Skip a tile for pingpong
|
||||
if (work_tile_info.is_valid()) {
|
||||
@@ -901,10 +1056,11 @@ public:
|
||||
mainloop_pipe_consumer_state.advance(work_k_tile_count);
|
||||
|
||||
// Go to next tile
|
||||
auto [next_next_work_tile_info, next_increment_pipe] = scheduler.fetch_next_work(work_tile_info);
|
||||
|
||||
work_tile_info = next_next_work_tile_info;
|
||||
increment_pipe = next_increment_pipe;
|
||||
auto [next_work_tile_info, increment_pipe] = scheduler.fetch_next_work(work_tile_info, tile_scheduler_pipeline, tile_scheduler_pipe_consumer_state);
|
||||
work_tile_info = next_work_tile_info;
|
||||
if (increment_pipe) {
|
||||
++tile_scheduler_pipe_consumer_state;
|
||||
}
|
||||
}
|
||||
|
||||
did_batch_change = curr_batch != work_tile_info.L_idx;
|
||||
@@ -951,7 +1107,7 @@ public:
|
||||
// Cue for next Math WG's Epilogue to start
|
||||
math_wg_order_barrier.arrive();
|
||||
|
||||
} // Scheduler work fetch loop
|
||||
} while (work_tile_info.is_valid()); // Scheduler work fetch loop
|
||||
} // Consumer Warp Groups End
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
#if defined(__CUDA_ARCH_FEAT_SM90_ALL)
|
||||
#if (defined(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL) || CUDA_ARCH_CONDITIONAL_OR_FAMILY(1200))
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the WGMMA ISA is arch conditional to sm90a.
|
||||
|
||||
@@ -341,7 +341,7 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
#if (defined(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL))
|
||||
#if (defined(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL) || CUDA_ARCH_CONDITIONAL_OR_FAMILY(1200))
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the ISA is arch conditional.
|
||||
|
||||
@@ -139,8 +139,8 @@ public:
|
||||
|
||||
/// Register requirement for Load and Math WGs
|
||||
static constexpr int RegsPerThread =
|
||||
size<0>(TileShape{}) * size<1>(TileShape{}) / NumMMAThreads *
|
||||
sizeof(ElementAccumulator) / sizeof(uint32_t);
|
||||
(size<0>(TileShape{}) * size<1>(TileShape{}) * sizeof(ElementAccumulator))
|
||||
/ (NumMMAThreads * sizeof(uint32_t));
|
||||
static constexpr bool HeavyRegisterPressure = RegsPerThread >= 208;
|
||||
static constexpr uint32_t LoadRegisterRequirement = !HeavyRegisterPressure ? 40 : 24;
|
||||
static constexpr uint32_t MmaRegisterRequirement = !HeavyRegisterPressure ? 232 : 240;
|
||||
@@ -352,7 +352,7 @@ public:
|
||||
using namespace cute;
|
||||
using X = Underscore;
|
||||
|
||||
#if (defined(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL))
|
||||
#if (defined(__CUDA_ARCH_FEAT_SM90_ALL) || defined(__CUDA_ARCH_FEAT_SM120_ALL) || CUDA_ARCH_CONDITIONAL_OR_FAMILY(1200))
|
||||
# define ENABLE_SM90_KERNEL_LEVEL 1
|
||||
#endif
|
||||
// Any Tensor Op MMA Atom in the ISA is arch conditional.
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace cutlass::gemm::kernel::detail {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Persistent Thread Block (TB) scheduler
|
||||
template <class GroupProblemShape>
|
||||
template <class GroupProblemShape, int SchedulerPipelineStageCount>
|
||||
class PersistentTileSchedulerSm90Group {
|
||||
//
|
||||
// Data members
|
||||
@@ -58,6 +58,7 @@ private:
|
||||
int group_idx = 0;
|
||||
uint64_t start_linear_idx = 0;
|
||||
uint64_t total_tiles = 0;
|
||||
uint64_t problem_blocks_along_raster_order = 0;
|
||||
} current_group_info_;
|
||||
|
||||
public:
|
||||
@@ -98,19 +99,34 @@ public:
|
||||
using RasterOrderOptions = typename Params::RasterOrderOptions;
|
||||
static constexpr bool IsDynamicPersistent = false;
|
||||
|
||||
using Pipeline = PipelineEmpty;
|
||||
using PipelineStorage = typename Pipeline::SharedStorage;
|
||||
// We need to hard code the number of stages here since the scheduling is static
|
||||
// and it can benefit from a larger number of stages without worrying about imbalances.
|
||||
|
||||
using Pipeline = PipelineAsync<SchedulerPipelineStageCount>;
|
||||
|
||||
// Call out the types here to work around a bug in MSVC.
|
||||
|
||||
// using PipelineStorage = typename Pipeline::SharedStorage;
|
||||
// using PipelineState = typename Pipeline::PipelineState;
|
||||
using PipelineStorage = cutlass::PipelineDetail::PipelineAsyncSharedStorage<SchedulerPipelineStageCount>;
|
||||
using PipelineState = cutlass::PipelineDetail::PipelineAsyncPipelineState<SchedulerPipelineStageCount>;
|
||||
|
||||
using ThrottlePipeline = PipelineEmpty;
|
||||
using ThrottlePipelineStorage = typename ThrottlePipeline::SharedStorage;
|
||||
struct CLCResponse {};
|
||||
using ThrottlePipelineStorage = typename PipelineEmpty::SharedStorage;
|
||||
using SchedulerResponse = WorkTileInfo;
|
||||
|
||||
class SharedStorage {
|
||||
public:
|
||||
CUTLASS_DEVICE PipelineStorage pipeline() { return PipelineStorage{}; }
|
||||
CUTLASS_DEVICE PipelineStorage pipeline() { return pipeline_; }
|
||||
// Pipeline throttle is not needed here as the scheduling is not dynamic.
|
||||
CUTLASS_DEVICE ThrottlePipelineStorage throttle_pipeline() { return ThrottlePipelineStorage{}; }
|
||||
CUTLASS_DEVICE CLCResponse* data() { return nullptr; }
|
||||
CUTLASS_DEVICE SchedulerResponse* data() { return data_; }
|
||||
|
||||
private:
|
||||
alignas(16) PipelineStorage pipeline_;
|
||||
alignas(16) SchedulerResponse data_[SchedulerPipelineStageCount];
|
||||
};
|
||||
|
||||
|
||||
struct Arguments {
|
||||
int max_swizzle_size = 1;
|
||||
// Not applying Heuristics for Grouped problems, since largest dimension can change per group
|
||||
@@ -119,6 +135,8 @@ public:
|
||||
|
||||
// Sink scheduler params as a member
|
||||
Params scheduler_params;
|
||||
SchedulerResponse *response_ptr_ = nullptr;
|
||||
ProblemShape cached_problem_shapes_[2];
|
||||
|
||||
//
|
||||
// Methods
|
||||
@@ -229,7 +247,7 @@ public:
|
||||
|
||||
PersistentTileSchedulerSm90Group() = default;
|
||||
|
||||
CUTLASS_DEVICE explicit PersistentTileSchedulerSm90Group(Params const& params_) : scheduler_params(params_) {
|
||||
CUTLASS_DEVICE explicit PersistentTileSchedulerSm90Group(Params const& params_, SchedulerResponse* response_ptr) : scheduler_params(params_), response_ptr_(response_ptr) {
|
||||
// MSVC requires protecting use of CUDA-specific nonstandard syntax,
|
||||
// like blockIdx and gridDim, with __CUDA_ARCH__.
|
||||
#if defined(__CUDA_ARCH__)
|
||||
@@ -240,8 +258,12 @@ public:
|
||||
current_work_linear_idx_ = uint64_t(blockIdx.x) * uint64_t(gridDim.y) + uint64_t(blockIdx.y);
|
||||
}
|
||||
|
||||
total_grid_size_ = uint64_t(gridDim.x) * uint64_t(gridDim.y) * uint64_t(gridDim.z);
|
||||
int lane_idx = canonical_lane_idx();
|
||||
if (lane_idx < params_.groups_) {
|
||||
cached_problem_shapes_[1] = params_.problem_shapes_[lane_idx];
|
||||
}
|
||||
|
||||
total_grid_size_ = uint64_t(gridDim.x) * uint64_t(gridDim.y) * uint64_t(gridDim.z);
|
||||
uint64_t ctas_along_m, ctas_along_n;
|
||||
if (is_tuple<decltype(cute::shape<0>(params_.problem_shapes_[0]))>::value ||
|
||||
is_tuple<decltype(cute::shape<1>(params_.problem_shapes_[0]))>::value) {
|
||||
@@ -255,52 +277,24 @@ public:
|
||||
auto problem_blocks_m = round_up(ctas_along_m, (1 << params_.log_swizzle_size_) * params_.cluster_shape_.m());
|
||||
auto problem_blocks_n = round_up(ctas_along_n, (1 << params_.log_swizzle_size_) * params_.cluster_shape_.n());
|
||||
current_group_info_.total_tiles = problem_blocks_m * problem_blocks_n;
|
||||
current_group_info_.problem_blocks_along_raster_order = params_.raster_order_ == RasterOrder::AlongN ? problem_blocks_n : problem_blocks_m;
|
||||
|
||||
#else
|
||||
CUTLASS_ASSERT(false && "This line should never be reached");
|
||||
#endif
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
get_current_work() {
|
||||
return get_current_work_for_linear_idx(current_work_linear_idx_);
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
get_current_work_for_linear_idx(uint64_t linear_idx) {
|
||||
if (scheduler_params.pre_processed_problem_shapes && linear_idx >= scheduler_params.blocks_across_problem_) {
|
||||
return WorkTileInfo::invalid_work_tile();
|
||||
}
|
||||
|
||||
return get_work_idx_m_and_n(linear_idx,
|
||||
current_group_info_,
|
||||
scheduler_params.groups_,
|
||||
scheduler_params.problem_shapes_,
|
||||
scheduler_params.cta_shape_,
|
||||
scheduler_params.cluster_shape_,
|
||||
scheduler_params.divmod_cluster_shape_major_,
|
||||
scheduler_params.divmod_cluster_shape_minor_,
|
||||
scheduler_params.divmod_cta_shape_m_,
|
||||
scheduler_params.divmod_cta_shape_n_,
|
||||
scheduler_params.log_swizzle_size_,
|
||||
scheduler_params.raster_order_);
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void
|
||||
advance_to_next_work(uint32_t advance_count = 1) {
|
||||
current_work_linear_idx_ += total_grid_size_ * uint64_t(advance_count);
|
||||
}
|
||||
|
||||
// get work_idx_m, work_idx_n from linear_idx while applying swizzle
|
||||
static CUTLASS_DEVICE
|
||||
template<class WorkTileInfo, class GroupInfo, class ProblemShape, class RasterOrder>
|
||||
static
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
get_work_idx_m_and_n(
|
||||
uint64_t linear_idx,
|
||||
struct GroupInfo& group_info,
|
||||
GroupInfo& group_info,
|
||||
int32_t total_problem_groups,
|
||||
ProblemShape* problem_shapes,
|
||||
ProblemShape (&cached_problem_shapes)[2],
|
||||
GemmCoord cta_shape,
|
||||
GemmCoord cluster_shape,
|
||||
FastDivmodU64Pow2 const& divmod_cluster_shape_major,
|
||||
@@ -311,39 +305,66 @@ public:
|
||||
RasterOrder raster_order) {
|
||||
|
||||
bool valid_tile = true;
|
||||
uint64_t ctas_along_m, ctas_along_n;
|
||||
if (is_tuple<decltype(cute::shape<0>(problem_shapes[group_info.group_idx]))>::value ||
|
||||
is_tuple<decltype(cute::shape<1>(problem_shapes[group_info.group_idx]))>::value) {
|
||||
ctas_along_m = cute::size(cute::ceil_div(cute::shape<0>(problem_shapes[group_info.group_idx]), cta_shape.m()));
|
||||
ctas_along_n = cute::size(cute::ceil_div(cute::shape<1>(problem_shapes[group_info.group_idx]), cta_shape.n()));
|
||||
}
|
||||
else {
|
||||
ctas_along_m = divmod_cta_shape_m.divide(cute::shape<0>(problem_shapes[group_info.group_idx]) + divmod_cta_shape_m.divisor - 1);
|
||||
ctas_along_n = divmod_cta_shape_n.divide(cute::shape<1>(problem_shapes[group_info.group_idx]) + divmod_cta_shape_n.divisor - 1);
|
||||
}
|
||||
auto problem_blocks_m = round_up(ctas_along_m, (1 << log_swizzle_size) * cluster_shape.m());
|
||||
auto problem_blocks_n = round_up(ctas_along_n, (1 << log_swizzle_size) * cluster_shape.n());
|
||||
group_info.total_tiles = problem_blocks_m * problem_blocks_n;
|
||||
|
||||
while (group_info.start_linear_idx + group_info.total_tiles <= linear_idx) {
|
||||
group_info.group_idx++;
|
||||
// Use a warp to "speculatively" check if the work tile maps to the next 32 groups
|
||||
int lane_idx = canonical_lane_idx();
|
||||
|
||||
if (group_info.group_idx >= total_problem_groups)
|
||||
return WorkTileInfo::invalid_work_tile();
|
||||
if (linear_idx >= group_info.total_tiles + group_info.start_linear_idx) {
|
||||
group_info.group_idx += lane_idx;
|
||||
for ( ; ; group_info.group_idx += NumThreadsPerWarp) {
|
||||
cached_problem_shapes[0] = cached_problem_shapes[1];
|
||||
if (group_info.group_idx + NumThreadsPerWarp < total_problem_groups) {
|
||||
cached_problem_shapes[1] = problem_shapes[group_info.group_idx + NumThreadsPerWarp];
|
||||
}
|
||||
if (group_info.group_idx < total_problem_groups) {
|
||||
uint64_t ctas_along_m, ctas_along_n;
|
||||
if (is_tuple<decltype(cute::shape<0>(cached_problem_shapes[0]))>::value ||
|
||||
is_tuple<decltype(cute::shape<1>(cached_problem_shapes[0]))>::value) {
|
||||
ctas_along_m = cute::size(cute::ceil_div(cute::shape<0>(cached_problem_shapes[0]), cta_shape.m()));
|
||||
ctas_along_n = cute::size(cute::ceil_div(cute::shape<1>(cached_problem_shapes[0]), cta_shape.n()));
|
||||
}
|
||||
else {
|
||||
ctas_along_m = divmod_cta_shape_m.divide(cute::shape<0>(cached_problem_shapes[0]) + divmod_cta_shape_m.divisor - 1);
|
||||
ctas_along_n = divmod_cta_shape_n.divide(cute::shape<1>(cached_problem_shapes[0]) + divmod_cta_shape_n.divisor - 1);
|
||||
}
|
||||
auto problem_blocks_m = round_up(ctas_along_m, (1 << log_swizzle_size) * cluster_shape.m());
|
||||
auto problem_blocks_n = round_up(ctas_along_n, (1 << log_swizzle_size) * cluster_shape.n());
|
||||
group_info.problem_blocks_along_raster_order = raster_order == RasterOrder::AlongN ? problem_blocks_n : problem_blocks_m;
|
||||
group_info.total_tiles = problem_blocks_m * problem_blocks_n;
|
||||
} else {
|
||||
group_info.total_tiles = INT_MAX;
|
||||
}
|
||||
|
||||
group_info.start_linear_idx += group_info.total_tiles;
|
||||
if (is_tuple<decltype(cute::shape<0>(problem_shapes[group_info.group_idx]))>::value ||
|
||||
is_tuple<decltype(cute::shape<1>(problem_shapes[group_info.group_idx]))>::value) {
|
||||
ctas_along_m = cute::size(cute::ceil_div(cute::shape<0>(problem_shapes[group_info.group_idx]), cta_shape.m()));
|
||||
ctas_along_n = cute::size(cute::ceil_div(cute::shape<1>(problem_shapes[group_info.group_idx]), cta_shape.n()));
|
||||
auto curr_total_tiles = group_info.total_tiles;
|
||||
|
||||
// Calculate prefix sum for start_linear_idx.
|
||||
#pragma unroll
|
||||
for (int i = 1; i < NumThreadsPerWarp; i *= 2) {
|
||||
auto n = __shfl_up_sync(0xffffffff, curr_total_tiles, i);
|
||||
curr_total_tiles = lane_idx >= i ? curr_total_tiles + n : curr_total_tiles;
|
||||
}
|
||||
group_info.start_linear_idx += curr_total_tiles - group_info.total_tiles;
|
||||
|
||||
uint32_t thread_succeed = __ballot_sync(0xffffffff, linear_idx < group_info.start_linear_idx + group_info.total_tiles);
|
||||
if (thread_succeed) {
|
||||
// Use the first succeeding thread.
|
||||
int first_succeeding_thread = __ffs(thread_succeed) - 1;
|
||||
group_info.group_idx = __shfl_sync(0xffffffff, group_info.group_idx, first_succeeding_thread);
|
||||
group_info.start_linear_idx = __shfl_sync(0xffffffff, group_info.start_linear_idx, first_succeeding_thread);
|
||||
group_info.total_tiles = __shfl_sync(0xffffffff, group_info.total_tiles, first_succeeding_thread);
|
||||
group_info.problem_blocks_along_raster_order = __shfl_sync(0xffffffff, group_info.problem_blocks_along_raster_order, first_succeeding_thread);
|
||||
if (group_info.group_idx + lane_idx < total_problem_groups) {
|
||||
cached_problem_shapes[1] = problem_shapes[group_info.group_idx + lane_idx];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Update the start_linear_idx for all threads so that they're ready for the next iteration.
|
||||
group_info.start_linear_idx = __shfl_sync(0xffffffff, group_info.start_linear_idx + group_info.total_tiles, NumThreadsPerWarp - 1);
|
||||
}
|
||||
else {
|
||||
ctas_along_m = divmod_cta_shape_m.divide(cute::shape<0>(problem_shapes[group_info.group_idx]) + divmod_cta_shape_m.divisor - 1);
|
||||
ctas_along_n = divmod_cta_shape_n.divide(cute::shape<1>(problem_shapes[group_info.group_idx]) + divmod_cta_shape_n.divisor - 1);
|
||||
}
|
||||
problem_blocks_m = round_up(ctas_along_m, (1 << log_swizzle_size) * cluster_shape.m());
|
||||
problem_blocks_n = round_up(ctas_along_n, (1 << log_swizzle_size) * cluster_shape.n());
|
||||
group_info.total_tiles = problem_blocks_m * problem_blocks_n;
|
||||
}
|
||||
|
||||
if (group_info.group_idx >= total_problem_groups) {
|
||||
return WorkTileInfo::invalid_work_tile();
|
||||
}
|
||||
|
||||
uint64_t cluster_id, cluster_major_offset = 0, cluster_minor_offset = 0;
|
||||
@@ -369,13 +390,8 @@ public:
|
||||
offset = cluster_id & ((1 << log_swizzle_size) - 1);
|
||||
extra = cluster_id >> log_swizzle_size;
|
||||
|
||||
uint64_t curr_group_cluster_blk_major;
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
curr_group_cluster_blk_major = divmod_cluster_shape_major.divide(problem_blocks_n);
|
||||
}
|
||||
else {
|
||||
curr_group_cluster_blk_major = divmod_cluster_shape_major.divide(problem_blocks_m);
|
||||
}
|
||||
uint64_t curr_group_cluster_blk_major = divmod_cluster_shape_major.divide(group_info.problem_blocks_along_raster_order);
|
||||
|
||||
cluster_idx_minor_div_swizzle = extra / curr_group_cluster_blk_major;
|
||||
cluster_idx_major = extra % curr_group_cluster_blk_major;
|
||||
|
||||
@@ -392,7 +408,46 @@ public:
|
||||
else {
|
||||
return {major_work_idx, minor_work_idx, group_info.group_idx, valid_tile};
|
||||
}
|
||||
}
|
||||
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
get_current_work_for_linear_idx(uint64_t linear_idx) {
|
||||
if (scheduler_params.pre_processed_problem_shapes && linear_idx >= scheduler_params.blocks_across_problem_) {
|
||||
return WorkTileInfo::invalid_work_tile();
|
||||
}
|
||||
return get_work_idx_m_and_n<WorkTileInfo>(
|
||||
linear_idx,
|
||||
current_group_info_,
|
||||
scheduler_params.groups_,
|
||||
scheduler_params.problem_shapes_,
|
||||
cached_problem_shapes_,
|
||||
scheduler_params.cta_shape_,
|
||||
scheduler_params.cluster_shape_,
|
||||
scheduler_params.divmod_cluster_shape_major_,
|
||||
scheduler_params.divmod_cluster_shape_minor_,
|
||||
scheduler_params.divmod_cta_shape_m_,
|
||||
scheduler_params.divmod_cta_shape_n_,
|
||||
scheduler_params.log_swizzle_size_,
|
||||
scheduler_params.raster_order_);
|
||||
}
|
||||
template <typename TileSchedulerPipeline, typename TileSchedulerPipelineState>
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
advance_to_next_work(
|
||||
TileSchedulerPipeline& scheduler_pipeline,
|
||||
TileSchedulerPipelineState scheduler_pipe_producer_state,
|
||||
uint32_t advance_count = 1) {
|
||||
|
||||
current_work_linear_idx_ += total_grid_size_ * uint64_t(advance_count);
|
||||
auto work_tile = get_current_work_for_linear_idx(current_work_linear_idx_);
|
||||
scheduler_pipeline.producer_acquire(scheduler_pipe_producer_state);
|
||||
if (cute::elect_one_sync()) {
|
||||
response_ptr_[scheduler_pipe_producer_state.index()] = work_tile;
|
||||
cutlass::arch::fence_view_async_shared();
|
||||
scheduler_pipeline.producer_commit(scheduler_pipe_producer_state);
|
||||
}
|
||||
return cute::make_tuple(work_tile, true);
|
||||
}
|
||||
|
||||
// Returns whether the block assigned this work should compute the epilogue for the corresponding
|
||||
@@ -503,25 +558,32 @@ public:
|
||||
}
|
||||
|
||||
// Kernel helper function to get next work tile
|
||||
template <typename TileSchedulerPipeline, typename TileSchedulerPipelineState>
|
||||
CUTLASS_DEVICE
|
||||
auto
|
||||
fetch_next_work(WorkTileInfo work_tile_info) {
|
||||
fetch_next_work(
|
||||
WorkTileInfo work_tile_info,
|
||||
TileSchedulerPipeline& scheduler_pipeline,
|
||||
TileSchedulerPipelineState scheduler_pipe_consumer_state) {
|
||||
|
||||
if (continue_current_work(work_tile_info)) {
|
||||
return cute::make_tuple(work_tile_info, true);
|
||||
}
|
||||
scheduler_pipeline.consumer_wait(scheduler_pipe_consumer_state);
|
||||
auto work_tile = response_ptr_[scheduler_pipe_consumer_state.index()];
|
||||
cutlass::arch::fence_view_async_shared();
|
||||
scheduler_pipeline.consumer_release(scheduler_pipe_consumer_state);
|
||||
|
||||
advance_to_next_work();
|
||||
return cute::make_tuple(get_current_work(), true);
|
||||
return cute::make_tuple(work_tile, true);
|
||||
}
|
||||
|
||||
// Returns the initial work tile info that will be computed over
|
||||
template <class ClusterShape>
|
||||
CUTLASS_DEVICE
|
||||
WorkTileInfo
|
||||
auto
|
||||
initial_work_tile_info(ClusterShape) {
|
||||
return get_current_work();
|
||||
return get_current_work_for_linear_idx(current_work_linear_idx_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace cutlass::gemm::kernel::detail
|
||||
|
||||
@@ -172,10 +172,9 @@ struct TileSchedulerSelector<
|
||||
, SchedulerPipelineStageCount
|
||||
, GroupProblemShape
|
||||
> {
|
||||
using Scheduler = PersistentTileSchedulerSm90Group<GroupProblemShape>;
|
||||
using Scheduler = PersistentTileSchedulerSm90Group<GroupProblemShape, SchedulerPipelineStageCount>;
|
||||
};
|
||||
|
||||
|
||||
template <class TileShape, class ClusterShape, uint32_t SchedulerPipelineStageCount>
|
||||
struct TileSchedulerSelector<
|
||||
PersistentScheduler,
|
||||
@@ -255,7 +254,7 @@ struct TileSchedulerSelector<
|
||||
SchedulerPipelineStageCount,
|
||||
GroupProblemShape
|
||||
> {
|
||||
using Scheduler = PersistentTileSchedulerSm100Group<GroupProblemShape>;
|
||||
using Scheduler = PersistentTileSchedulerSm100Group<GroupProblemShape, SchedulerPipelineStageCount>;
|
||||
};
|
||||
|
||||
// SM100 stream-K scheduler
|
||||
@@ -339,6 +338,24 @@ struct TileSchedulerSelector<
|
||||
SchedulerPipelineStageCount>;
|
||||
};
|
||||
|
||||
// SM120 Group tile scheduler
|
||||
template <
|
||||
class TileShape,
|
||||
class ClusterShape,
|
||||
uint32_t SchedulerPipelineStageCount,
|
||||
class GroupProblemShape
|
||||
>
|
||||
struct TileSchedulerSelector<
|
||||
GroupScheduler,
|
||||
arch::Sm120,
|
||||
TileShape,
|
||||
ClusterShape,
|
||||
SchedulerPipelineStageCount,
|
||||
GroupProblemShape
|
||||
> {
|
||||
using Scheduler = PersistentTileSchedulerSm90Group<GroupProblemShape, SchedulerPipelineStageCount>;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass::gemm::kernel::detail
|
||||
|
||||
@@ -1097,6 +1097,10 @@ struct PersistentTileSchedulerSm90StreamKParams {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Ensure that the number of SK tiles is divisible by cluster size so that it can be evenly
|
||||
// divided among SK clusters.
|
||||
sk_tiles = (sk_tiles / cluster_size) * cluster_size;
|
||||
|
||||
return static_cast<uint32_t>(sk_tiles);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user