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:
Yujia Zhai
2025-04-24 15:42:40 -04:00
committed by GitHub
co-authored by yuzhai Haicheng Wu
parent 8e345c5c5b
commit 331a1f5b3f
143 changed files with 18089 additions and 5935 deletions
@@ -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__)
@@ -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 ? &params.tma_load_a_fallback : &params.tma_load_a;
observed_tma_load_b_ = is_fallback_cluster ? &params.tma_load_b_fallback : &params.tma_load_b;
}
else {
observed_tma_load_a_ = &params.tma_load_a;
observed_tma_load_b_ = &params.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
@@ -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);
}
}
@@ -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>();
+104 -71
View File
@@ -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) {
@@ -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) {
@@ -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;
};
@@ -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
+20 -3
View File
@@ -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);
}