CUTLASS 3.2.1 (#1113)
* Updates for 3.2.1 release. * Minor fix in gemm op profiler for raster order. * Add scheduler mapping for raster order in the kernels.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief
|
||||
Default configuration for a GEMM with fused epilogue visitor callbacks
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/gemm/kernel/default_gemm_universal.h"
|
||||
|
||||
#include "cutlass/gemm/kernel/gemm_universal_with_visitor.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_with_visitor_streamk.h"
|
||||
#include "cutlass/epilogue/threadblock/epilogue_with_visitor_callbacks.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
/// Element type for A matrix operand
|
||||
typename ElementA_,
|
||||
/// Layout type for A matrix operand
|
||||
typename LayoutA_,
|
||||
/// Complex elementwise transformation on A operand
|
||||
ComplexTransform TransformA,
|
||||
/// Access granularity of A matrix in units of elements
|
||||
int kAlignmentA,
|
||||
/// Element type for B matrix operand
|
||||
typename ElementB_,
|
||||
/// Layout type for B matrix operand
|
||||
typename LayoutB_,
|
||||
/// Complex elementwise transformation on B operand
|
||||
ComplexTransform TransformB,
|
||||
/// Access granularity of B matrix in units of elements
|
||||
int kAlignmentB,
|
||||
/// Element type for C and D matrix operands
|
||||
typename ElementC_,
|
||||
/// Layout type for C and D matrix operands
|
||||
typename LayoutC_,
|
||||
/// Access granularity of C matrix in unit of elements
|
||||
int kAlignmentC,
|
||||
/// Element type for internal accumulation
|
||||
typename ElementAccumulator,
|
||||
/// Element type for epilogue computation
|
||||
typename ElementEpilogue,
|
||||
/// Operator class tag
|
||||
typename OperatorClass,
|
||||
/// Tag indicating architecture to tune for
|
||||
typename ArchTag,
|
||||
/// Threadblock-level tile size (concept: GemmShape)
|
||||
typename ThreadblockShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename WarpShape,
|
||||
/// Warp-level tile size (concept: GemmShape)
|
||||
typename InstructionShape,
|
||||
/// Epilogue output operator
|
||||
typename FusionCallbacks,
|
||||
/// Threadblock-level swizzling operator
|
||||
typename ThreadblockSwizzle,
|
||||
/// Number of stages used in the pipelined mainloop
|
||||
int Stages,
|
||||
/// Operation performed by GEMM
|
||||
typename Operator,
|
||||
/// Number of stages used in the pipelined epilogue
|
||||
int EpilogueStages = 1
|
||||
>
|
||||
struct DefaultGemmWithVisitor {
|
||||
|
||||
using GemmBase = typename DefaultGemmUniversal<
|
||||
ElementA_, LayoutA_, TransformA, kAlignmentA,
|
||||
ElementB_, LayoutB_, TransformB, kAlignmentB,
|
||||
ElementC_, LayoutC_, ElementAccumulator,
|
||||
OperatorClass,
|
||||
ArchTag,
|
||||
ThreadblockShape,
|
||||
WarpShape,
|
||||
InstructionShape,
|
||||
epilogue::thread::LinearCombination<
|
||||
ElementC_, kAlignmentC,
|
||||
ElementAccumulator, ElementEpilogue
|
||||
>,
|
||||
ThreadblockSwizzle,
|
||||
Stages,
|
||||
Operator
|
||||
>::GemmKernel;
|
||||
|
||||
// Define epilogue
|
||||
using Epilogue = cutlass::epilogue::threadblock::EpilogueWithVisitorCallbacks<
|
||||
typename GemmBase::Epilogue,
|
||||
FusionCallbacks,
|
||||
EpilogueStages
|
||||
>;
|
||||
|
||||
/// GemmWithVisitor without StreamkFeature member type
|
||||
template <class SwizzleT, class Enable = void>
|
||||
class SelectBase :
|
||||
public GemmWithEpilogueVisitor<
|
||||
typename GemmBase::Mma,
|
||||
Epilogue,
|
||||
SwizzleT>
|
||||
{};
|
||||
|
||||
/// GemmWIthVisitor with StreamkFeature member type
|
||||
template <class SwizzleT>
|
||||
class SelectBase<SwizzleT, typename SwizzleT::StreamkFeature> :
|
||||
public GemmWithEpilogueVisitorStreamk<
|
||||
typename GemmBase::Mma,
|
||||
Epilogue,
|
||||
SwizzleT>
|
||||
{};
|
||||
|
||||
/// Select kernel by ThreadblockSwizzle's support for StreamkFeature
|
||||
using GemmKernel = SelectBase<ThreadblockSwizzle>;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -39,7 +39,6 @@
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/matrix_coord.h"
|
||||
#include "cutlass/gemm/kernel/grouped_problem_visitor.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
#include "cutlass/layout/matrix.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/gemm/kernel/params_universal_base.h"
|
||||
|
||||
#include "cutlass/trace.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -346,8 +345,8 @@ public:
|
||||
|
||||
output_op = args.epilogue;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/// Shared memory storage structure
|
||||
union SharedStorage {
|
||||
@@ -465,13 +464,14 @@ public:
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void operator()(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
{
|
||||
|
||||
// Compute threadblock location
|
||||
void operator()(Params const ¶ms, SharedStorage &shared_storage) {
|
||||
ThreadblockSwizzle threadblock_swizzle;
|
||||
run_with_swizzle(params, shared_storage, threadblock_swizzle);
|
||||
}
|
||||
|
||||
/// Executes one GEMM with an externally-provided swizzling function
|
||||
CUTLASS_DEVICE
|
||||
void run_with_swizzle(Params const ¶ms, SharedStorage &shared_storage, ThreadblockSwizzle& threadblock_swizzle) {
|
||||
|
||||
cutlass::gemm::GemmCoord threadblock_tile_offset =
|
||||
threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief Gemm kernel with an epilogue defined under the epilogue visitor concept
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Gemm that compute the epilogue visitor functor
|
||||
template <
|
||||
typename Mma, ///! Threadblock-scoped matrix multiply-accumulate
|
||||
typename Epilogue, ///! Epilogue
|
||||
typename ThreadblockSwizzle_ ///! Threadblock swizzling function
|
||||
>
|
||||
class GemmWithEpilogueVisitor: GemmUniversal<Mma,Epilogue, ThreadblockSwizzle_> {
|
||||
public:
|
||||
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
|
||||
using Base = GemmUniversal<Mma,Epilogue, ThreadblockSwizzle>;
|
||||
using Base::Base;
|
||||
|
||||
using FusionCallbacks = typename Epilogue::FusionCallbacks;
|
||||
|
||||
using ElementA = typename Base::ElementA;
|
||||
using LayoutA = typename Base::LayoutA;
|
||||
using ElementB = typename Base::ElementB;
|
||||
using LayoutB = typename Base::LayoutB;
|
||||
using ElementC = typename Base::ElementC;
|
||||
using LayoutC = typename Base::LayoutC;
|
||||
|
||||
using ThreadblockShape = typename Mma::Shape;
|
||||
|
||||
//
|
||||
// Structures
|
||||
//
|
||||
|
||||
using SharedStorage = typename Base::SharedStorage;
|
||||
using Arguments = typename Base::Arguments;
|
||||
|
||||
//
|
||||
// Structure for precomputing values in host memory and passing to kernels
|
||||
//
|
||||
|
||||
/// Parameters structure
|
||||
struct Params : UniversalParamsBase<
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>
|
||||
{
|
||||
using ParamsBase = UniversalParamsBase<
|
||||
ThreadblockSwizzle,
|
||||
ThreadblockShape,
|
||||
ElementA,
|
||||
ElementB,
|
||||
ElementC,
|
||||
LayoutA,
|
||||
LayoutB>;
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
cute::Shape<int32_t,int32_t,int32_t> problem_shape;
|
||||
|
||||
typename Mma::IteratorA::Params params_A;
|
||||
typename Mma::IteratorB::Params params_B;
|
||||
typename FusionCallbacks::Params output_op;
|
||||
|
||||
void * ptr_A;
|
||||
void * ptr_B;
|
||||
|
||||
int64_t batch_stride_A;
|
||||
int64_t batch_stride_B;
|
||||
|
||||
int * ptr_gather_A_indices;
|
||||
int * ptr_gather_B_indices;
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
Params() = default;
|
||||
|
||||
/// Constructor
|
||||
Params(
|
||||
Arguments const &args, /// GEMM application arguments
|
||||
int device_sms, /// Number of SMs on the device
|
||||
int sm_occupancy) /// Kernel SM occupancy (in thread blocks)
|
||||
:
|
||||
ParamsBase(args, device_sms, sm_occupancy),
|
||||
params_A(args.lda ? make_Coord_with_padding<LayoutA::kStrideRank>(args.lda) : args.stride_a),
|
||||
params_B(args.ldb ? make_Coord_with_padding<LayoutB::kStrideRank>(args.ldb) : args.stride_b),
|
||||
output_op(FusionCallbacks::to_underlying_arguments(args.problem_size, args.epilogue, nullptr /*workspace*/)),
|
||||
problem_shape({args.problem_size.m(), args.problem_size.n(), args.batch_count}),
|
||||
ptr_A(const_cast<void *>(args.ptr_A)),
|
||||
ptr_B(const_cast<void *>(args.ptr_B)),
|
||||
batch_stride_A(args.batch_stride_A),
|
||||
batch_stride_B(args.batch_stride_B),
|
||||
ptr_gather_A_indices(const_cast<int *>(args.ptr_gather_A_indices)),
|
||||
ptr_gather_B_indices(const_cast<int *>(args.ptr_gather_B_indices))
|
||||
{
|
||||
// Raise error on unsupported modes
|
||||
assert(args.mode != GemmUniversalMode::kGemmSplitKParallel && "Sm80 EVT does not support SplitKParallel.");
|
||||
assert(!(args.mode == GemmUniversalMode::kGemm && this->grid_tiled_shape.k() > 1 )
|
||||
&& "Sm80 EVT does not support SplitKSerial.");
|
||||
assert(args.mode != GemmUniversalMode::kArray && "Sm80 EVT does not support Array Gemm.");
|
||||
}
|
||||
|
||||
/// Lightweight update given a subset of arguments.
|
||||
void update(Arguments const &args)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversalwithVisitor::Params::update()");
|
||||
|
||||
// Update input pointers
|
||||
ptr_A = const_cast<void *>(args.ptr_A);
|
||||
ptr_B = const_cast<void *>(args.ptr_B);
|
||||
|
||||
batch_stride_A = args.batch_stride_A;
|
||||
batch_stride_B = args.batch_stride_B;
|
||||
this->batch_stride_D = args.batch_stride_D;
|
||||
|
||||
ptr_gather_A_indices = const_cast<int *>(args.ptr_gather_A_indices);
|
||||
ptr_gather_B_indices = const_cast<int *>(args.ptr_gather_B_indices);
|
||||
|
||||
output_op = FusionCallbacks::to_underlying_arguments(args.problem_size, args.epilogue, nullptr /*workspace*/);
|
||||
problem_shape = make_shape(args.problem_size.m(), args.problem_size.n(), args.batch_count);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Device-only API
|
||||
//
|
||||
|
||||
// Factory invocation
|
||||
CUTLASS_DEVICE
|
||||
static void invoke(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
{
|
||||
GemmWithEpilogueVisitor op;
|
||||
op(params, shared_storage);
|
||||
}
|
||||
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void operator()(Params const ¶ms, SharedStorage &shared_storage) {
|
||||
ThreadblockSwizzle threadblock_swizzle;
|
||||
run_with_swizzle(params, shared_storage, threadblock_swizzle);
|
||||
}
|
||||
|
||||
/// Executes one GEMM with an externally-provided swizzling function
|
||||
CUTLASS_DEVICE
|
||||
void run_with_swizzle(Params const ¶ms, SharedStorage &shared_storage, ThreadblockSwizzle& threadblock_swizzle) {
|
||||
|
||||
cutlass::gemm::GemmCoord threadblock_tile_offset =
|
||||
threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
// Early exit if CTA is out of range
|
||||
if (params.grid_tiled_shape.m() <= threadblock_tile_offset.m() ||
|
||||
params.grid_tiled_shape.n() <= threadblock_tile_offset.n()) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int offset_k = 0;
|
||||
int problem_size_k = params.problem_size.k();
|
||||
|
||||
ElementA *ptr_A = static_cast<ElementA *>(params.ptr_A);
|
||||
ElementB *ptr_B = static_cast<ElementB *>(params.ptr_B);
|
||||
|
||||
//
|
||||
// Fetch pointers based on mode.
|
||||
//
|
||||
if (params.mode == GemmUniversalMode::kGemm) {
|
||||
|
||||
if (threadblock_tile_offset.k() + 1 < params.grid_tiled_shape.k()) {
|
||||
|
||||
problem_size_k = (threadblock_tile_offset.k() + 1) * params.gemm_k_size;
|
||||
}
|
||||
|
||||
offset_k = threadblock_tile_offset.k() * params.gemm_k_size;
|
||||
}
|
||||
else if (params.mode == GemmUniversalMode::kBatched) {
|
||||
ptr_A += threadblock_tile_offset.k() * params.batch_stride_A;
|
||||
ptr_B += threadblock_tile_offset.k() * params.batch_stride_B;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Compute initial location in logical coordinates
|
||||
cutlass::MatrixCoord tb_offset_A{
|
||||
threadblock_tile_offset.m() * Mma::Shape::kM,
|
||||
offset_k,
|
||||
};
|
||||
|
||||
cutlass::MatrixCoord tb_offset_B{
|
||||
offset_k,
|
||||
threadblock_tile_offset.n() * Mma::Shape::kN
|
||||
};
|
||||
|
||||
// Compute position within threadblock
|
||||
int thread_idx = threadIdx.x;
|
||||
|
||||
// Construct iterators to A and B operands
|
||||
typename Mma::IteratorA iterator_A(
|
||||
params.params_A,
|
||||
ptr_A,
|
||||
{params.problem_size.m(), problem_size_k},
|
||||
thread_idx,
|
||||
tb_offset_A,
|
||||
params.ptr_gather_A_indices);
|
||||
|
||||
typename Mma::IteratorB iterator_B(
|
||||
params.params_B,
|
||||
ptr_B,
|
||||
{problem_size_k, params.problem_size.n()},
|
||||
thread_idx,
|
||||
tb_offset_B,
|
||||
params.ptr_gather_B_indices);
|
||||
|
||||
// Broadcast the warp_id computed by lane 0 to ensure dependent code
|
||||
// is compiled as warp-uniform.
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
|
||||
int lane_idx = threadIdx.x % 32;
|
||||
|
||||
//
|
||||
// Main loop
|
||||
//
|
||||
|
||||
// Construct thread-scoped matrix multiply
|
||||
Mma mma(shared_storage.main_loop, thread_idx, warp_idx, lane_idx);
|
||||
|
||||
typename Mma::FragmentC accumulators;
|
||||
|
||||
accumulators.clear();
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
int gemm_k_iterations = (problem_size_k - offset_k + Mma::Shape::kK - 1) / Mma::Shape::kK;
|
||||
|
||||
// Compute threadblock-scoped matrix multiply-add
|
||||
mma(
|
||||
gemm_k_iterations,
|
||||
accumulators,
|
||||
iterator_A,
|
||||
iterator_B,
|
||||
accumulators);
|
||||
|
||||
//
|
||||
// Epilogue
|
||||
//
|
||||
|
||||
threadblock_tile_offset = threadblock_swizzle.get_tile_offset(params.swizzle_log_tile);
|
||||
|
||||
Epilogue epilogue(
|
||||
params.output_op,
|
||||
shared_storage.epilogue,
|
||||
thread_idx,
|
||||
warp_idx,
|
||||
lane_idx);
|
||||
|
||||
// Execute the epilogue operator to update the destination tensor.
|
||||
epilogue(accumulators, threadblock_tile_offset, params.problem_shape, thread_idx);
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -0,0 +1,892 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
|
||||
/*! \file
|
||||
\brief Gemm kernel with an epilogue defined under the epilogue visitor concept with streamk.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/fast_math.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
#include "cutlass/matrix_coord.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/barrier.h"
|
||||
#include "cutlass/block_striped.h"
|
||||
|
||||
#include "cutlass/trace.h"
|
||||
#include "cutlass/gemm/kernel/gemm_universal_streamk.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace kernel {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <
|
||||
typename Mma_, ///! Threadblock-scoped matrix multiply-accumulate
|
||||
typename Epilogue_, ///! Epilogue
|
||||
typename ThreadblockSwizzle_ ///! Threadblock mapping function
|
||||
>
|
||||
class GemmWithEpilogueVisitorStreamk {
|
||||
public:
|
||||
|
||||
using Base = GemmUniversalStreamk<Mma_, Epilogue_, ThreadblockSwizzle_>;
|
||||
|
||||
//
|
||||
// Types and constants
|
||||
//
|
||||
|
||||
using Mma = Mma_;
|
||||
using Epilogue = Epilogue_;
|
||||
using FusionCallbacks = typename Epilogue::FusionCallbacks;
|
||||
using EpilogueOutputOp = typename Epilogue::OutputOp;
|
||||
using ThreadblockSwizzle = ThreadblockSwizzle_;
|
||||
|
||||
using ElementA = typename Mma::IteratorA::Element;
|
||||
using LayoutA = typename Mma::IteratorA::Layout;
|
||||
using ElementB = typename Mma::IteratorB::Element;
|
||||
using LayoutB = typename Mma::IteratorB::Layout;
|
||||
using ElementC = typename Epilogue::OutputTileIterator::Element;
|
||||
using LayoutC = typename Epilogue::OutputTileIterator::Layout;
|
||||
|
||||
/// The per-thread tile of raw accumulators
|
||||
using AccumulatorTile = typename Mma::FragmentC;
|
||||
|
||||
static ComplexTransform const kTransformA = Mma::kTransformA;
|
||||
static ComplexTransform const kTransformB = Mma::kTransformB;
|
||||
using Operator = typename Mma::Operator;
|
||||
|
||||
using OperatorClass = typename Mma::Operator::OperatorClass;
|
||||
using ThreadblockShape = typename Mma::Shape;
|
||||
using WarpShape = typename Mma::Operator::Shape;
|
||||
using InstructionShape = typename Mma::Policy::Operator::InstructionShape;
|
||||
using ArchTag = typename Mma::ArchTag;
|
||||
|
||||
static int const kStages = Mma::kStages;
|
||||
static int const kAlignmentA = Mma::IteratorA::AccessType::kElements;
|
||||
static int const kAlignmentB = Mma::IteratorB::AccessType::kElements;
|
||||
static int const kAlignmentC = Epilogue::OutputTileIterator::kElementsPerAccess;
|
||||
|
||||
/// Warp count (concept: GemmShape)
|
||||
using WarpCount = typename Mma::WarpCount;
|
||||
static int const kThreadCount = 32 * WarpCount::kCount;
|
||||
|
||||
/// Workspace bytes per thread block
|
||||
static size_t const kWorkspaceBytesPerBlock =
|
||||
__NV_STD_MAX(
|
||||
kThreadCount * sizeof(AccumulatorTile),
|
||||
Epilogue::kWorkspaceBytesPerBlock);
|
||||
|
||||
/// Block-striped reduction utility
|
||||
using BlockStripedReduceT = BlockStripedReduce<kThreadCount, AccumulatorTile>;
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Structures
|
||||
//
|
||||
|
||||
using Arguments = typename Base::Arguments;
|
||||
|
||||
|
||||
/// Parameters structure
|
||||
struct Params
|
||||
{
|
||||
public:
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
cute::Shape<int32_t,int32_t,int32_t> problem_shape;
|
||||
|
||||
void * ptr_A;
|
||||
void * ptr_B;
|
||||
|
||||
typename Mma::IteratorA::Params params_A;
|
||||
typename Mma::IteratorB::Params params_B;
|
||||
|
||||
int64_t batch_stride_A;
|
||||
int64_t batch_stride_B;
|
||||
|
||||
GemmUniversalMode mode;
|
||||
|
||||
ThreadblockSwizzle block_mapping;
|
||||
|
||||
void *barrier_workspace;
|
||||
void *partials_workspace;
|
||||
|
||||
typename FusionCallbacks::Params output_op;
|
||||
|
||||
|
||||
void * ptr_D;
|
||||
void * ptr_C;
|
||||
|
||||
typename Epilogue::OutputTileIterator::Params params_D;
|
||||
typename Epilogue::OutputTileIterator::Params params_C;
|
||||
|
||||
int64_t batch_stride_D;
|
||||
int64_t batch_stride_C;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
//
|
||||
// Host-only dispatch-utilities
|
||||
//
|
||||
|
||||
/// Pad the given allocation size up to the nearest cache line
|
||||
static size_t cacheline_align_up(size_t size)
|
||||
{
|
||||
static const int CACHELINE_SIZE = 128;
|
||||
return (size + CACHELINE_SIZE - 1) / CACHELINE_SIZE * CACHELINE_SIZE;
|
||||
}
|
||||
|
||||
/// Get the workspace size needed for barrier
|
||||
size_t get_barrier_workspace_size() const
|
||||
{
|
||||
// For atomic reduction, each SK-block needs a synchronization flag. For parallel reduction,
|
||||
// each reduction block needs its own synchronization flag.
|
||||
int sk_blocks = block_mapping.sk_regions() * block_mapping.sk_blocks_per_region();
|
||||
int num_flags = fast_max(sk_blocks, block_mapping.reduction_blocks);
|
||||
|
||||
return cacheline_align_up(sizeof(typename Barrier::T) * num_flags);
|
||||
}
|
||||
|
||||
/// Get the workspace size needed for intermediate partial sums
|
||||
size_t get_partials_workspace_size() const
|
||||
{
|
||||
int sk_blocks = block_mapping.sk_regions() * block_mapping.sk_blocks_per_region();
|
||||
return cacheline_align_up(kWorkspaceBytesPerBlock * sk_blocks);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Host dispatch API
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
Params() = default;
|
||||
|
||||
|
||||
/// Constructor
|
||||
Params(
|
||||
Arguments const &args, /// GEMM application arguments
|
||||
int device_sms, /// Number of SMs on the device
|
||||
int sm_occupancy) /// Kernel SM occupancy (in thread blocks)
|
||||
:
|
||||
problem_shape({args.problem_size.m(), args.problem_size.n(), args.batch_count}),
|
||||
params_A(args.lda ? make_Coord_with_padding<LayoutA::kStrideRank>(args.lda) : args.stride_a),
|
||||
params_B(args.ldb ? make_Coord_with_padding<LayoutB::kStrideRank>(args.ldb) : args.stride_b),
|
||||
params_C(args.ldc ? make_Coord_with_padding<LayoutC::kStrideRank>(args.ldc) : args.stride_c),
|
||||
params_D(args.ldd ? make_Coord_with_padding<LayoutC::kStrideRank>(args.ldd) : args.stride_d),
|
||||
output_op(FusionCallbacks::to_underlying_arguments(args.problem_size, args.epilogue, nullptr /*workspace*/)),
|
||||
mode(args.mode),
|
||||
ptr_A(const_cast<void *>(args.ptr_A)),
|
||||
ptr_B(const_cast<void *>(args.ptr_B)),
|
||||
ptr_C(const_cast<void *>(args.ptr_C)),
|
||||
ptr_D(args.ptr_D),
|
||||
batch_stride_A(args.batch_stride_A),
|
||||
batch_stride_B(args.batch_stride_B),
|
||||
batch_stride_C(args.batch_stride_C),
|
||||
batch_stride_D(args.batch_stride_D),
|
||||
barrier_workspace(nullptr),
|
||||
partials_workspace(nullptr)
|
||||
{
|
||||
// Number of SMs to make available for StreamK decomposition
|
||||
int avail_sms = (args.avail_sms == -1) ?
|
||||
device_sms :
|
||||
fast_min(args.avail_sms, device_sms);
|
||||
|
||||
// Initialize the block mapping structure
|
||||
block_mapping = ThreadblockSwizzle(
|
||||
typename ThreadblockSwizzle::template KernelTraits<GemmWithEpilogueVisitorStreamk>(),
|
||||
args.mode,
|
||||
args.problem_size,
|
||||
{ThreadblockShape::kM, ThreadblockShape::kN, ThreadblockShape::kK},
|
||||
args.batch_count,
|
||||
sm_occupancy,
|
||||
device_sms,
|
||||
avail_sms);
|
||||
}
|
||||
|
||||
|
||||
/// Returns the workspace size (in bytes) needed for these parameters
|
||||
size_t get_workspace_size() const
|
||||
{
|
||||
return
|
||||
get_barrier_workspace_size() +
|
||||
get_partials_workspace_size();
|
||||
}
|
||||
|
||||
|
||||
/// Assign and initialize the specified workspace buffer. Assumes
|
||||
/// the memory allocated to workspace is at least as large as get_workspace_size().
|
||||
Status init_workspace(
|
||||
void *workspace,
|
||||
cudaStream_t stream = nullptr)
|
||||
{
|
||||
uint8_t *ptr = static_cast<uint8_t*>(workspace);
|
||||
|
||||
// Establish partials workspace
|
||||
partials_workspace = nullptr;
|
||||
size_t partials_workspace_bytes = get_partials_workspace_size();
|
||||
if (partials_workspace_bytes > 0)
|
||||
{
|
||||
if (!workspace) {
|
||||
return Status::kErrorWorkspaceNull;
|
||||
}
|
||||
partials_workspace = ptr;
|
||||
ptr += partials_workspace_bytes;
|
||||
}
|
||||
|
||||
// Establish barrier workspace
|
||||
barrier_workspace = nullptr;
|
||||
size_t barrier_workspace_bytes = get_barrier_workspace_size();
|
||||
if (barrier_workspace_bytes > 0)
|
||||
{
|
||||
if (!workspace) {
|
||||
return Status::kErrorWorkspaceNull;
|
||||
}
|
||||
barrier_workspace = ptr;
|
||||
ptr += barrier_workspace_bytes;
|
||||
}
|
||||
|
||||
// Zero-initialize barrier workspace
|
||||
if (barrier_workspace)
|
||||
{
|
||||
size_t barrier_workspace_bytes = get_barrier_workspace_size();
|
||||
|
||||
CUTLASS_TRACE_HOST(" Initialize " << barrier_workspace_bytes << " barrier bytes");
|
||||
|
||||
cudaError_t result = cudaMemsetAsync(
|
||||
barrier_workspace,
|
||||
0,
|
||||
barrier_workspace_bytes,
|
||||
stream);
|
||||
|
||||
if (result != cudaSuccess) {
|
||||
CUTLASS_TRACE_HOST(" cudaMemsetAsync() returned error " << cudaGetErrorString(result));
|
||||
return Status::kErrorInternal;
|
||||
}
|
||||
}
|
||||
|
||||
return Status::kSuccess;
|
||||
}
|
||||
|
||||
|
||||
/// Returns the GEMM volume in thread block tiles
|
||||
cutlass::gemm::GemmCoord get_tiled_shape() const
|
||||
{
|
||||
return block_mapping.tiled_shape();
|
||||
}
|
||||
|
||||
|
||||
/// Returns the total number of thread blocks to launch
|
||||
int get_grid_blocks() const
|
||||
{
|
||||
dim3 grid_dims = get_grid_dims();
|
||||
return grid_dims.x * grid_dims.y * grid_dims.z;
|
||||
}
|
||||
|
||||
|
||||
/// Returns the grid extents in thread blocks to launch
|
||||
dim3 get_grid_dims() const
|
||||
{
|
||||
return block_mapping.get_grid_dims();
|
||||
}
|
||||
|
||||
|
||||
/// Lightweight update given a subset of arguments.
|
||||
void update(Arguments const &args)
|
||||
{
|
||||
CUTLASS_TRACE_HOST("GemmUniversalStreamK::Params::update()");
|
||||
|
||||
// Update input/output pointers
|
||||
ptr_A = const_cast<void *>(args.ptr_A);
|
||||
ptr_B = const_cast<void *>(args.ptr_B);
|
||||
ptr_C = const_cast<void *>(args.ptr_C);
|
||||
ptr_D = args.ptr_D;
|
||||
|
||||
batch_stride_A = args.batch_stride_A;
|
||||
batch_stride_B = args.batch_stride_B;
|
||||
batch_stride_C = args.batch_stride_C;
|
||||
batch_stride_D = args.batch_stride_D;
|
||||
|
||||
output_op = FusionCallbacks::to_underlying_arguments(args.problem_size, args.epilogue, nullptr /*workspace*/);
|
||||
problem_shape = make_shape(args.problem_size.m(), args.problem_size.n(), args.batch_count);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct TileWorkDesc: Base::TileWorkDesc {
|
||||
int k_end;
|
||||
CUTLASS_DEVICE
|
||||
bool tile_finished(Params const ¶ms)
|
||||
{
|
||||
return (k_end == params.block_mapping.problem_size.k());
|
||||
}
|
||||
};
|
||||
|
||||
// using TileWorkDesc = typename Base::TileWorkDesc;
|
||||
using SharedStorage = typename Base::SharedStorage;
|
||||
|
||||
protected:
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// GEMM problem parameters
|
||||
Params params;
|
||||
|
||||
/// Shared storage reference
|
||||
SharedStorage &shared_storage;
|
||||
|
||||
/// ID within the threadblock
|
||||
int thread_idx;
|
||||
|
||||
/// ID of warp
|
||||
int warp_idx;
|
||||
|
||||
/// ID of each thread within a warp
|
||||
int lane_idx;
|
||||
|
||||
/// Threadblock scoped epilogue
|
||||
Epilogue epilogue;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Host-only dispatch API
|
||||
//
|
||||
|
||||
/// Determines whether the GEMM problem size satisfies this kernel's
|
||||
/// alignment requirements
|
||||
static Status can_implement(
|
||||
cutlass::gemm::GemmCoord const & problem_size)
|
||||
{
|
||||
return Base::can_implement(problem_size);
|
||||
}
|
||||
|
||||
/// Determines whether the GEMM problem satisfies this kernel's
|
||||
/// alignment requirements
|
||||
static Status can_implement(Arguments const &args) {
|
||||
return can_implement(args.problem_size);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
//
|
||||
// Device-only utility methods
|
||||
//
|
||||
|
||||
/// Iterator for fetching tile fragments from A
|
||||
CUTLASS_DEVICE
|
||||
typename Mma::IteratorA init_iterator_A(
|
||||
TileWorkDesc &tile_work,
|
||||
GemmUniversalMode mode)
|
||||
{
|
||||
// The input A matrix
|
||||
ElementA *ptr_A = static_cast<ElementA *>(params.ptr_A);
|
||||
|
||||
// Update input pointers based on batched/array mode
|
||||
if (mode == GemmUniversalMode::kBatched) {
|
||||
ptr_A += tile_work.tiled_coord.k() * params.batch_stride_A;
|
||||
}
|
||||
if (mode == GemmUniversalMode::kArray) {
|
||||
ptr_A = static_cast<ElementA * const *>(params.ptr_A)[tile_work.tiled_coord.k()];
|
||||
}
|
||||
|
||||
int m_begin = tile_work.tiled_coord.m() * Mma::Shape::kM;
|
||||
int m_end = params.block_mapping.problem_size.m();
|
||||
return Mma::IteratorA(
|
||||
params.params_A,
|
||||
ptr_A,
|
||||
{ m_end, tile_work.k_end },
|
||||
threadIdx.x,
|
||||
{ m_begin, tile_work.k_begin });
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Iterator for fetching tile fragments from B
|
||||
CUTLASS_DEVICE
|
||||
typename Mma::IteratorB init_iterator_B(
|
||||
TileWorkDesc &tile_work,
|
||||
GemmUniversalMode mode)
|
||||
{
|
||||
// The input B matrix
|
||||
ElementB *ptr_B = static_cast<ElementB *>(params.ptr_B);
|
||||
|
||||
// Update input pointers based on batched/array mode
|
||||
if (mode == GemmUniversalMode::kBatched) {
|
||||
ptr_B += tile_work.tiled_coord.k() * params.batch_stride_B;
|
||||
}
|
||||
if (mode == GemmUniversalMode::kArray) {
|
||||
ptr_B = static_cast<ElementB * const *>(params.ptr_B)[tile_work.tiled_coord.k()];
|
||||
}
|
||||
|
||||
int n_begin = tile_work.tiled_coord.n() * Mma::Shape::kN;
|
||||
int n_end = params.block_mapping.problem_size.n();
|
||||
return Mma::IteratorB(
|
||||
params.params_B,
|
||||
ptr_B,
|
||||
{ tile_work.k_end, n_end },
|
||||
threadIdx.x,
|
||||
{ tile_work.k_begin, n_begin });
|
||||
}
|
||||
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void init_dp_tile_work(
|
||||
TileWorkDesc &tile_work,
|
||||
int tile_idx)
|
||||
{
|
||||
// The linear tile index
|
||||
tile_work.tile_idx = tile_idx;
|
||||
|
||||
// The first global-scoped MAC-iteration this threadblock will perform for this tile
|
||||
tile_work.iter_begin = tile_idx * params.block_mapping.iters_per_tile();
|
||||
|
||||
// The number of MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_iters_remaining = params.block_mapping.iters_per_tile();
|
||||
|
||||
// The starting index in the k-domain for MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_begin = 0;
|
||||
|
||||
// The ending index (one-past) in the k-domain for MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_end = params.block_mapping.problem_size.k();
|
||||
|
||||
// The location of this tile (in threadblock-tile coordinates) in the output matrix
|
||||
tile_work.tiled_coord = params.block_mapping.get_tile_offset(tile_work.tile_idx);
|
||||
}
|
||||
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void init_sk_tile_work(
|
||||
TileWorkDesc &tile_work,
|
||||
int tile_idx,
|
||||
int block_iter_begin,
|
||||
int block_iter_end)
|
||||
{
|
||||
// The linear tile index
|
||||
tile_work.tile_idx = tile_idx;
|
||||
|
||||
// The first global-scoped MAC-iteration for this tile
|
||||
int tile_iter_begin = tile_idx * params.block_mapping.iters_per_tile();
|
||||
|
||||
// The first global-scoped MAC-iteration this threadblock will perform for this tile
|
||||
tile_work.iter_begin = max(block_iter_begin, tile_iter_begin);
|
||||
|
||||
// The first tile-scoped MAC-iteration this threadblock will perform for this tile
|
||||
int k_iter_begin = tile_work.iter_begin - tile_iter_begin;
|
||||
|
||||
// The last (one past) tile-scoped MAC-iteration this threadblock will perform for this tile
|
||||
int k_iter_end = block_iter_end - tile_iter_begin;
|
||||
|
||||
// The number of MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_iters_remaining = k_iter_end - k_iter_begin;
|
||||
|
||||
// The starting index in the k-domain for MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_begin = k_iter_begin * Mma::Shape::kK;
|
||||
|
||||
// The ending index (one-past) in the k-domain for MAC-iterations this threadblock will perform for this tile
|
||||
tile_work.k_end = min(
|
||||
params.block_mapping.problem_size.k(), // extent of k domain
|
||||
(k_iter_end * Mma::Shape::kK)); // extent of the threadblock's global iteration assignment
|
||||
|
||||
// The location of this tile (in threadblock-tile coordinates) in the output matrix
|
||||
tile_work.tiled_coord = params.block_mapping.get_tile_offset(tile_work.tile_idx);
|
||||
}
|
||||
|
||||
|
||||
/// Share accumulators with peers
|
||||
CUTLASS_DEVICE
|
||||
void share_accumulators(
|
||||
AccumulatorTile const &accumulator_tile,
|
||||
int block_idx,
|
||||
int first_block_idx)
|
||||
{
|
||||
AccumulatorTile *accum_tile_workspace = reinterpret_cast<AccumulatorTile *>(params.partials_workspace);
|
||||
|
||||
int accum_tile_offset = first_block_idx * kThreadCount;
|
||||
|
||||
if (block_idx == first_block_idx)
|
||||
{
|
||||
// First peer initializes the workspace partials
|
||||
BlockStripedReduceT::store(accum_tile_workspace + accum_tile_offset, accumulator_tile, thread_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subsequent peers atomically accumulate into the workspace partials
|
||||
if (ThreadblockSwizzle::kReductionStrategy == ThreadblockSwizzle::kAtomic)
|
||||
{
|
||||
// Non-deterministic reduction order: wait for the first peer to have initialized the partials before we add to them
|
||||
Barrier::wait_lt(params.barrier_workspace, thread_idx, first_block_idx, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Turnstile reduction order: wait until the previous peer has written
|
||||
int wait_count = block_idx - first_block_idx;
|
||||
Barrier::wait_eq(params.barrier_workspace, thread_idx, first_block_idx, wait_count);
|
||||
}
|
||||
|
||||
// Perform reduction in workspace
|
||||
BlockStripedReduceT::reduce(accum_tile_workspace + accum_tile_offset, accumulator_tile, thread_idx);
|
||||
}
|
||||
|
||||
// Signal our arrival
|
||||
Barrier::arrive_inc(params.barrier_workspace, thread_idx, first_block_idx);
|
||||
}
|
||||
|
||||
|
||||
/// Acquire accumulators from peers
|
||||
CUTLASS_DEVICE
|
||||
void acquire_accumulators(
|
||||
AccumulatorTile &accumulator_tile,
|
||||
int block_idx,
|
||||
int first_block_idx)
|
||||
{
|
||||
AccumulatorTile *accum_tile_workspace = reinterpret_cast<AccumulatorTile *>(params.partials_workspace);
|
||||
|
||||
// Wait for arrival
|
||||
int num_carry_in = block_idx - first_block_idx;
|
||||
Barrier::wait_eq_reset(params.barrier_workspace, thread_idx, first_block_idx, num_carry_in);
|
||||
|
||||
// Load and add peer-partials accumulator tile to local accumulator tile
|
||||
int accum_tile_offset = first_block_idx * kThreadCount;
|
||||
BlockStripedReduceT::load_add(accumulator_tile, accum_tile_workspace + accum_tile_offset, thread_idx);
|
||||
}
|
||||
|
||||
|
||||
/// Perform epilogue computations and output
|
||||
CUTLASS_DEVICE
|
||||
void do_epilogue(
|
||||
TileWorkDesc &tile_work,
|
||||
AccumulatorTile &accumulator_tile)
|
||||
{
|
||||
cutlass::gemm::GemmCoord threadblock_tile_offset{
|
||||
tile_work.tiled_coord.m(),
|
||||
tile_work.tiled_coord.n(),
|
||||
tile_work.tiled_coord.k()
|
||||
};
|
||||
|
||||
// Execute the epilogue operator to update the destination tensor.
|
||||
epilogue(
|
||||
accumulator_tile,
|
||||
threadblock_tile_offset,
|
||||
params.problem_shape,
|
||||
thread_idx);
|
||||
}
|
||||
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void separate_reduction(int reduce_idx)
|
||||
{
|
||||
int peer_idx_begin, peer_idx_last, reduce_tile_idx, reduce_fragment_idx;
|
||||
|
||||
// Reduce by sk-tile (every tile contributed to by one or more blocks)
|
||||
reduce_tile_idx = reduce_idx / Epilogue::kAccumulatorFragments;
|
||||
reduce_fragment_idx = reduce_idx % Epilogue::kAccumulatorFragments;
|
||||
|
||||
int iter_tile_first = reduce_tile_idx * params.block_mapping.iters_per_tile();
|
||||
int iter_tile_last = iter_tile_first + params.block_mapping.iters_per_tile() - 1;
|
||||
|
||||
peer_idx_begin = params.block_mapping.get_sk_block_idx(iter_tile_first);
|
||||
peer_idx_last = params.block_mapping.get_sk_block_idx(iter_tile_last);
|
||||
|
||||
// Wait for peers to complete
|
||||
int peer_idx_end = peer_idx_last + 1;
|
||||
int num_peers = peer_idx_end - peer_idx_begin;
|
||||
Barrier::wait_eq_reset(
|
||||
params.barrier_workspace,
|
||||
thread_idx,
|
||||
(reduce_tile_idx * Epilogue::kAccumulatorFragments) + reduce_fragment_idx,
|
||||
num_peers);
|
||||
|
||||
/// The location of this tile (in threadblock-tile coordinates) in the output matrix
|
||||
GemmCoord tiled_coord = params.block_mapping.get_tile_offset(reduce_tile_idx);
|
||||
|
||||
// Execute the epilogue operator to update the destination tensor.
|
||||
epilogue.reduce(
|
||||
peer_idx_begin,
|
||||
peer_idx_end,
|
||||
reduce_fragment_idx,
|
||||
params.partials_workspace,
|
||||
tiled_coord,
|
||||
params.problem_shape,
|
||||
thread_idx);
|
||||
}
|
||||
|
||||
|
||||
CUTLASS_DEVICE
|
||||
void process_tile(
|
||||
TileWorkDesc tile_work,
|
||||
int block_idx,
|
||||
int dp_start_block_idx,
|
||||
int block_iter_begin)
|
||||
{
|
||||
// Initialize input iterators
|
||||
typename Mma::IteratorA iterator_A = init_iterator_A(tile_work, params.mode);
|
||||
typename Mma::IteratorB iterator_B = init_iterator_B(tile_work, params.mode);
|
||||
|
||||
// Initialize accumulators
|
||||
AccumulatorTile accumulator_tile;
|
||||
accumulator_tile.clear();
|
||||
|
||||
// Initialize MMA abstraction
|
||||
Mma mma(
|
||||
shared_storage.main_loop,
|
||||
thread_idx,
|
||||
warp_idx,
|
||||
lane_idx);
|
||||
|
||||
// Perform this tile's range of multiply-accumulate (MAC) iterations
|
||||
mma(tile_work.k_iters_remaining, accumulator_tile, iterator_A, iterator_B, accumulator_tile);
|
||||
|
||||
if ((ThreadblockSwizzle::kReductionStrategy == ThreadblockSwizzle::kAtomic) ||
|
||||
(params.block_mapping.reduction_blocks == 0) ||
|
||||
(block_idx >= dp_start_block_idx))
|
||||
{
|
||||
//
|
||||
// Cooperative SK peer reduction or DP block
|
||||
//
|
||||
|
||||
int first_block_idx = params.block_mapping.get_first_block_idx(tile_work.tile_idx, block_idx);
|
||||
|
||||
if (!tile_work.tile_finished(params)) {
|
||||
// Non "finishing" SK blocks must share their partial accumulator sums through global scratch workspace
|
||||
share_accumulators(accumulator_tile, block_idx, first_block_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// DP blocks and "finishing" SK blocks must perform epilogue operations and write the output tile
|
||||
if (!tile_work.tile_started())
|
||||
{
|
||||
// A "finishing" SK block must first aggregate its accumulator partial sums with those shared by peer threadblocks
|
||||
acquire_accumulators(accumulator_tile, block_idx, first_block_idx);
|
||||
}
|
||||
|
||||
do_epilogue(tile_work, accumulator_tile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Separate peer reduction
|
||||
//
|
||||
|
||||
// Share accumulator partial sums with peer threadblock(s) through scratch workspace
|
||||
epilogue.share(block_idx, params.partials_workspace, accumulator_tile, tile_work.tile_started());
|
||||
|
||||
// Signal arrival
|
||||
Barrier::arrive_range_inc(
|
||||
params.barrier_workspace,
|
||||
thread_idx,
|
||||
tile_work.tile_idx * Epilogue::kAccumulatorFragments,
|
||||
Epilogue::kAccumulatorFragments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void gemm()
|
||||
{
|
||||
// Initialize block's iteration range
|
||||
int tile_idx = 0;
|
||||
int block_iter_begin = 0;
|
||||
int block_iters_remaining = 0;
|
||||
|
||||
int block_idx = params.block_mapping.get_block_idx();
|
||||
|
||||
int sk_padding_start_block_idx = params.block_mapping.sk_regions() * params.block_mapping.sk_blocks_per_region();
|
||||
int dp_start_block_idx = params.block_mapping.sk_waves * params.block_mapping.avail_sms;
|
||||
int reduce_start_block_idx = dp_start_block_idx + params.block_mapping.dp_blocks;
|
||||
int grid_padding_start_block_idx = reduce_start_block_idx + params.block_mapping.reduction_blocks;
|
||||
|
||||
// Initialize tile work descriptor
|
||||
TileWorkDesc tile_work;
|
||||
|
||||
bool dp_block = (block_idx >= dp_start_block_idx) && (block_idx < reduce_start_block_idx);
|
||||
bool sk_block = (block_idx < sk_padding_start_block_idx);
|
||||
bool reduce_block = (block_idx >= reduce_start_block_idx) &&
|
||||
(block_idx < grid_padding_start_block_idx) &&
|
||||
(ThreadblockSwizzle::kReductionStrategy == ThreadblockSwizzle::kMixed);
|
||||
|
||||
if (dp_block)
|
||||
{
|
||||
// This is a DP block
|
||||
int dp_block_idx = block_idx - dp_start_block_idx;
|
||||
int first_dp_tile = (params.block_mapping.cohort_raster) ? 0 : params.block_mapping.sk_tiles;
|
||||
|
||||
// Blocks in first DP wave get configured number of tiles
|
||||
tile_idx = first_dp_tile + dp_block_idx;
|
||||
int tile_allottment = params.block_mapping.dp_first_wave_tiles;
|
||||
|
||||
// Blocks in subsequent DP waves get 1 tile
|
||||
if (dp_block_idx >= params.block_mapping.avail_sms) {
|
||||
tile_allottment = 1;
|
||||
tile_idx += (params.block_mapping.dp_first_wave_tiles - 1) * params.block_mapping.avail_sms;
|
||||
}
|
||||
|
||||
block_iters_remaining = params.block_mapping.iters_per_tile() * tile_allottment;
|
||||
|
||||
init_dp_tile_work(tile_work, tile_idx);
|
||||
|
||||
// DP blocks exit if out of bounds or overlap an SK tile (only possible during cohort rasterization, where dp_first_wave_tiles must be 1)
|
||||
if ((tile_idx < params.block_mapping.sk_tiles) ||
|
||||
(tile_work.tiled_coord.m() >= params.block_mapping.tiled_shape().m()) ||
|
||||
(tile_work.tiled_coord.n() >= params.block_mapping.tiled_shape().n()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (sk_block)
|
||||
{
|
||||
// This is a SK block
|
||||
int block_iter_end;
|
||||
params.block_mapping.get_iter_extents(block_idx, block_iter_begin, block_iter_end);
|
||||
block_iters_remaining = block_iter_end - block_iter_begin;
|
||||
|
||||
tile_idx = params.block_mapping.get_sk_tile_idx(block_iter_end - 1);
|
||||
init_sk_tile_work(tile_work, tile_idx, block_iter_begin, block_iter_begin + block_iters_remaining);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reduce_block)
|
||||
{
|
||||
// This is a reduction threadblock
|
||||
int reduce_block_idx = block_idx - reduce_start_block_idx;
|
||||
separate_reduction(reduce_block_idx);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Iteration-processing loop body
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
while (true)
|
||||
{
|
||||
// Perform this block's share of work for this tile
|
||||
process_tile(
|
||||
tile_work,
|
||||
block_idx,
|
||||
dp_start_block_idx,
|
||||
block_iter_begin);
|
||||
|
||||
block_iters_remaining -= tile_work.k_iters_remaining;
|
||||
|
||||
if (block_iters_remaining == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue to next tile
|
||||
__syncthreads();
|
||||
|
||||
if (block_idx >= dp_start_block_idx)
|
||||
{
|
||||
// DP block consume their tiles at stride
|
||||
tile_idx += params.block_mapping.avail_sms;
|
||||
init_dp_tile_work(tile_work, tile_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SK blocks consume their tiles in backwards order
|
||||
tile_idx--;
|
||||
init_sk_tile_work(tile_work, tile_idx, block_iter_begin, block_iter_begin + block_iters_remaining);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Device-only API
|
||||
//
|
||||
|
||||
// Factory invocation
|
||||
CUTLASS_DEVICE
|
||||
static void invoke(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
{
|
||||
GemmWithEpilogueVisitorStreamk op(params, shared_storage);
|
||||
op();
|
||||
}
|
||||
|
||||
|
||||
CUTLASS_DEVICE
|
||||
GemmWithEpilogueVisitorStreamk(
|
||||
Params const ¶ms,
|
||||
SharedStorage &shared_storage)
|
||||
:
|
||||
params(params),
|
||||
shared_storage(shared_storage),
|
||||
thread_idx(threadIdx.x),
|
||||
warp_idx(__shfl_sync(0xffffffff, threadIdx.x / 32, 0)), // broadcast the warp_id computed by lane 0 to ensure dependent code
|
||||
lane_idx(threadIdx.x % 32),
|
||||
epilogue(
|
||||
params.output_op,
|
||||
shared_storage.epilogue,
|
||||
thread_idx,
|
||||
warp_idx,
|
||||
lane_idx)
|
||||
{}
|
||||
|
||||
|
||||
/// Executes one GEMM
|
||||
CUTLASS_DEVICE
|
||||
void operator()()
|
||||
{
|
||||
// Generic SK code path
|
||||
gemm();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -51,7 +51,8 @@ namespace kernel {
|
||||
namespace util {
|
||||
|
||||
template <class LayoutA, class LayoutB>
|
||||
static inline bool
|
||||
CUTLASS_HOST_DEVICE
|
||||
static bool
|
||||
is_continous_k_aligned(GemmCoord problem_size, size_t alignmentA, size_t alignmentB) {
|
||||
return (std::is_same<LayoutA, layout::RowMajor>::value && (problem_size.k() % alignmentA) == 0) ||
|
||||
(std::is_same<LayoutB, layout::ColumnMajor>::value && (problem_size.k() % alignmentB) == 0);
|
||||
@@ -149,42 +150,9 @@ struct UniversalParamsBase
|
||||
batch_stride_D(args.batch_stride_D),
|
||||
semaphore(nullptr)
|
||||
{
|
||||
ThreadblockSwizzle swizzle;
|
||||
|
||||
// Get GEMM volume in thread block tiles
|
||||
grid_tiled_shape = swizzle.get_tiled_shape(
|
||||
args.problem_size,
|
||||
{ThreadblockShape::kM, ThreadblockShape::kN, ThreadblockShape::kK},
|
||||
args.batch_count);
|
||||
|
||||
swizzle_log_tile = swizzle.get_log_tile(grid_tiled_shape);
|
||||
|
||||
// Determine extent of K-dimension assigned to each block
|
||||
gemm_k_size = args.problem_size.k();
|
||||
|
||||
if (args.mode == GemmUniversalMode::kGemm || args.mode == GemmUniversalMode::kGemmSplitKParallel)
|
||||
{
|
||||
static const uint32_t CACHELINE_BYTES = 128;
|
||||
static const size_t element_bytes_a = sizeof(ElementA);
|
||||
static const size_t element_bytes_b = sizeof(ElementB);
|
||||
static const size_t cacheline_elements_a = CACHELINE_BYTES / element_bytes_a;
|
||||
static const size_t cacheline_elements_b = CACHELINE_BYTES / element_bytes_b;
|
||||
|
||||
const bool cacheline_alignment_needed =
|
||||
util::is_continous_k_aligned<LayoutA, LayoutB>(problem_size, cacheline_elements_a, cacheline_elements_b);
|
||||
|
||||
int const kAlignK = const_max(
|
||||
const_max(128 / sizeof_bits<ElementA>::value, 128 / sizeof_bits<ElementB>::value),
|
||||
cacheline_alignment_needed ? const_max(cacheline_elements_a, cacheline_elements_b) : 1);
|
||||
|
||||
gemm_k_size = round_up(ceil_div(args.problem_size.k(), args.batch_count), kAlignK);
|
||||
if (gemm_k_size) {
|
||||
grid_tiled_shape.k() = ceil_div(args.problem_size.k(), gemm_k_size);
|
||||
}
|
||||
}
|
||||
init_grid_tiled_shape();
|
||||
}
|
||||
|
||||
|
||||
/// Returns the workspace size (in bytes) needed for this problem geometry
|
||||
size_t get_workspace_size() const
|
||||
{
|
||||
@@ -259,6 +227,41 @@ struct UniversalParamsBase
|
||||
return ThreadblockSwizzle().get_grid_shape(grid_tiled_shape);
|
||||
}
|
||||
|
||||
private:
|
||||
CUTLASS_HOST_DEVICE
|
||||
void init_grid_tiled_shape() {
|
||||
// Get GEMM volume in thread block tiles
|
||||
grid_tiled_shape = ThreadblockSwizzle::get_tiled_shape(
|
||||
problem_size,
|
||||
{ThreadblockShape::kM, ThreadblockShape::kN, ThreadblockShape::kK},
|
||||
batch_count);
|
||||
|
||||
swizzle_log_tile = ThreadblockSwizzle::get_log_tile(grid_tiled_shape);
|
||||
|
||||
// Determine extent of K-dimension assigned to each block
|
||||
gemm_k_size = problem_size.k();
|
||||
|
||||
if (mode == GemmUniversalMode::kGemm || mode == GemmUniversalMode::kGemmSplitKParallel)
|
||||
{
|
||||
static const uint32_t CACHELINE_BYTES = 128;
|
||||
static const size_t element_bytes_a = sizeof(ElementA);
|
||||
static const size_t element_bytes_b = sizeof(ElementB);
|
||||
static const size_t cacheline_elements_a = CACHELINE_BYTES / element_bytes_a;
|
||||
static const size_t cacheline_elements_b = CACHELINE_BYTES / element_bytes_b;
|
||||
|
||||
const bool cacheline_alignment_needed =
|
||||
util::is_continous_k_aligned<LayoutA, LayoutB>(problem_size, cacheline_elements_a, cacheline_elements_b);
|
||||
|
||||
int const kAlignK = const_max(
|
||||
const_max(128 / sizeof_bits<ElementA>::value, 128 / sizeof_bits<ElementB>::value),
|
||||
cacheline_alignment_needed ? const_max(cacheline_elements_a, cacheline_elements_b) : 1);
|
||||
|
||||
gemm_k_size = round_up(ceil_div(problem_size.k(), batch_count), kAlignK);
|
||||
if (gemm_k_size) {
|
||||
grid_tiled_shape.k() = ceil_div(problem_size.k(), gemm_k_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
|
||||
static_assert(cute::is_void_v<TileScheduler_> or cute::is_same_v<TileScheduler_, PersistentScheduler>,
|
||||
"SM70 kernel does not support specializing the tile scheduler.");
|
||||
using TileScheduleTag = TileScheduler_;
|
||||
using TileSchedulerTag = TileScheduler_;
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
TileScheduler_, ArchTag, TileShape,
|
||||
cute::Shape<cute::Int<1>, cute::Int<1>, cute::Int<1>>>::Scheduler;
|
||||
|
||||
@@ -105,14 +105,14 @@ public:
|
||||
using StrideC = typename CollectiveEpilogue::StrideC;
|
||||
using ElementD = typename CollectiveEpilogue::ElementD;
|
||||
using StrideD = typename CollectiveEpilogue::StrideD;
|
||||
using EpilogueArguments = typename CollectiveEpilogue::Params;
|
||||
using EpilogueArguments = typename CollectiveEpilogue::Arguments;
|
||||
using EpilogueParams = typename CollectiveEpilogue::Params;
|
||||
static_assert(cute::is_same_v<ElementAccumulator, typename CollectiveEpilogue::ElementAccumulator>,
|
||||
"Mainloop and epilogue do not agree on accumulator value type.");
|
||||
|
||||
static_assert(cute::is_void_v<TileScheduler_> or cute::is_same_v<TileScheduler_, PersistentScheduler>,
|
||||
"TMA kernel does not support specializing the tile scheduler.");
|
||||
using TileScheduleTag = TileScheduler_;
|
||||
using TileSchedulerTag = TileScheduler_;
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
TileScheduler_, ArchTag, TileShape, ClusterShape>::Scheduler;
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
|
||||
static_assert(cute::is_void_v<TileScheduler_> or cute::is_same_v<TileScheduler_, PersistentScheduler>,
|
||||
"TMA warp-specialized kernel does not support specializing the tile scheduler.");
|
||||
using TileScheduleTag = TileScheduler_;
|
||||
using TileSchedulerTag = TileScheduler_;
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
TileScheduler_, ArchTag, TileShape, ClusterShape>::Scheduler;
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
@@ -357,8 +357,6 @@ public:
|
||||
// Get pipeline iterators and increments from tensor shapes
|
||||
auto k_tile_iter = cute::make_coord_iterator(shape<2>(gA));
|
||||
auto k_tile_count = size<2>(gA);
|
||||
auto c_tile_count = CollectiveEpilogue::get_load_pipe_increment(blk_shape);
|
||||
[[maybe_unused]] auto d_tile_count = CollectiveEpilogue::get_store_pipe_increment(blk_shape);
|
||||
|
||||
// Wait for all thread blocks in the Cluster
|
||||
cluster_wait_fn();
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
|
||||
static_assert(ArchTag::kMinComputeCapability >= 90);
|
||||
|
||||
using TileScheduleTag = TileScheduler_;
|
||||
using TileSchedulerTag = TileScheduler_;
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
TileScheduler_, ArchTag, TileShape, ClusterShape>::Scheduler;
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
@@ -238,6 +238,7 @@ public:
|
||||
if constexpr (!std::is_const_v<decltype(args.max_swizzle_size)>) {
|
||||
args.max_swizzle_size = 1 << params.scheduler.log_swizzle_size_;
|
||||
}
|
||||
args.raster_order = params.scheduler.raster_order_ == TileScheduler::RasterOrder::AlongN ? TileScheduler::RasterOrderOptions::AlongN : TileScheduler::RasterOrderOptions::AlongM;
|
||||
return TileScheduler::get_grid_shape(params.problem_shape, TileShape{}, ClusterShape{}, params.hw_info, args);
|
||||
}
|
||||
|
||||
@@ -390,8 +391,6 @@ public:
|
||||
|
||||
// Get pipeline stage increments from tensor shapes
|
||||
auto k_tile_count = size<3>(gA_mkl);
|
||||
auto c_tile_count = CollectiveEpilogue::get_load_pipe_increment(blk_shape);
|
||||
auto d_tile_count = CollectiveEpilogue::get_store_pipe_increment(blk_shape);
|
||||
|
||||
TileScheduler scheduler{params.scheduler};
|
||||
auto work_tile_info = scheduler.get_current_work();
|
||||
@@ -417,15 +416,13 @@ public:
|
||||
auto blk_coord = make_coord(m_coord, n_coord, _, l_coord);
|
||||
|
||||
// Slice with our work tile coordinates to construct mainloop tensor views
|
||||
Tensor gA_presplit = gA_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gB_presplit = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
Tensor gA = gA_mkl(_,_,m_coord,_,l_coord); // (BLK_M,BLK_K,k)
|
||||
Tensor gB = gB_nkl(_,_,n_coord,_,l_coord); // (BLK_N,BLK_K,k)
|
||||
|
||||
// Split operands A and B along the K dimension according to work_tile_info
|
||||
Tensor gA = TileScheduler::split_MK(gA_presplit, work_tile_info); // (BLK_N,BLK_K,k_split_iters)
|
||||
Tensor gB = TileScheduler::split_NK(gB_presplit, work_tile_info); // (BLK_N,BLK_K,k_split_iters)
|
||||
|
||||
auto work_k_tile_count = size<2>(gA);
|
||||
auto k_tile_iter = cute::make_coord_iterator(shape<2>(gA_presplit));
|
||||
// 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<2>(gA)), shape<2>(gA));
|
||||
|
||||
collective_mainloop.load(
|
||||
mainloop_pipeline,
|
||||
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
|
||||
static_assert(cute::is_void_v<TileScheduler_> or cute::is_same_v<TileScheduler_, PersistentScheduler>,
|
||||
"Ping-pong kernel only supports the default scheduler.");
|
||||
using TileScheduleTag = TileScheduler_;
|
||||
using TileSchedulerTag = TileScheduler_;
|
||||
using TileScheduler = typename detail::TileSchedulerSelector<
|
||||
TileScheduler_, ArchTag, TileShape, ClusterShape>::Scheduler;
|
||||
using TileSchedulerArguments = typename TileScheduler::Arguments;
|
||||
@@ -240,6 +240,7 @@ public:
|
||||
if constexpr (!std::is_const_v<decltype(args.max_swizzle_size)>) {
|
||||
args.max_swizzle_size = 1 << params.scheduler.log_swizzle_size_;
|
||||
}
|
||||
args.raster_order = params.scheduler.raster_order_ == TileScheduler::RasterOrder::AlongN ? TileScheduler::RasterOrderOptions::AlongN : TileScheduler::RasterOrderOptions::AlongM;
|
||||
return TileScheduler::get_grid_shape(params.problem_shape, TileShape{}, ClusterShape{}, params.hw_info, args);
|
||||
}
|
||||
|
||||
@@ -282,7 +283,7 @@ public:
|
||||
|
||||
// Kernel level shared memory storage
|
||||
SharedStorage& shared_storage = *reinterpret_cast<SharedStorage*>(smem_buf);
|
||||
|
||||
|
||||
int thread_idx = int(threadIdx.x);
|
||||
int lane_idx = canonical_lane_idx();
|
||||
int warp_idx = canonical_warp_idx_sync();
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/fast_math.h"
|
||||
#include "cutlass/gemm_coord.hpp"
|
||||
#include "cutlass/kernel_hardware_info.hpp"
|
||||
#include "cutlass/gemm/kernel/tile_scheduler_params.h"
|
||||
#include "cute/layout.hpp"
|
||||
#include "cute/tensor.hpp"
|
||||
#include "cute/arch/cluster_sm90.hpp"
|
||||
@@ -57,30 +59,15 @@ public:
|
||||
bool is_valid_tile = false;
|
||||
};
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
enum class RasterOrder {
|
||||
AlongM,
|
||||
AlongN
|
||||
};
|
||||
using Params = PersistentTileSchedulerSm90Params;
|
||||
using RasterOrder = typename Params::RasterOrder;
|
||||
using RasterOrderOptions = typename Params::RasterOrderOptions;
|
||||
|
||||
struct Arguments {
|
||||
int max_swizzle_size = 1;
|
||||
RasterOrderOptions raster_order = RasterOrderOptions::Heuristic;
|
||||
};
|
||||
|
||||
struct Params {
|
||||
|
||||
FastDivmodU64 divmod_cluster_shape_major_{};
|
||||
FastDivmodU64 divmod_cluster_shape_minor_{};
|
||||
FastDivmodU64 divmod_batch_{};
|
||||
FastDivmodU64 divmod_cluster_blk_major_{};
|
||||
|
||||
uint64_t blocks_per_problem_ = 0;
|
||||
int32_t log_swizzle_size_ = 0;
|
||||
RasterOrder raster_order_ = RasterOrder::AlongN;
|
||||
};
|
||||
// Sink scheduler params as a member
|
||||
Params scheduler_params;
|
||||
|
||||
@@ -102,40 +89,18 @@ public:
|
||||
static_assert(cute::is_static<TileShape>::value);
|
||||
static_assert(cute::is_static<ClusterShape>::value);
|
||||
|
||||
// Round up to nearest multiple of cluster dim along each mode
|
||||
auto [problem_blocks_m, problem_blocks_n, problem_blocks_l] = get_tiled_cta_shape_mnl(
|
||||
problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
|
||||
// Round up to nearest multiple of swizzle_size along each mode
|
||||
auto log_swizzle_size = get_log_swizzle_size(problem_blocks_m, problem_blocks_n, arguments.max_swizzle_size);
|
||||
problem_blocks_m = round_up(problem_blocks_m, (1 << log_swizzle_size) * cute::size<0>(cluster_shape));
|
||||
problem_blocks_n = round_up(problem_blocks_n, (1 << log_swizzle_size) * cute::size<1>(cluster_shape));
|
||||
|
||||
Params params;
|
||||
params.initialize(
|
||||
problem_blocks,
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
arguments.max_swizzle_size,
|
||||
arguments.raster_order
|
||||
);
|
||||
|
||||
RasterOrder raster_order;
|
||||
raster_order = get_rasterization_order(problem_shape_mnkl, tile_shape);
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
return {
|
||||
FastDivmodU64(cute::size<1>(cluster_shape)),
|
||||
FastDivmodU64(cute::size<0>(cluster_shape)),
|
||||
FastDivmodU64(problem_blocks_m * problem_blocks_n),
|
||||
FastDivmodU64(problem_blocks_n / cute::size<1>(cluster_shape)),
|
||||
problem_blocks_m * problem_blocks_n * problem_blocks_l,
|
||||
log_swizzle_size,
|
||||
raster_order
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
FastDivmodU64(cute::size<0>(cluster_shape)),
|
||||
FastDivmodU64(cute::size<1>(cluster_shape)),
|
||||
FastDivmodU64(problem_blocks_m * problem_blocks_n),
|
||||
FastDivmodU64(problem_blocks_m / cute::size<0>(cluster_shape)),
|
||||
problem_blocks_m * problem_blocks_n * problem_blocks_l,
|
||||
log_swizzle_size,
|
||||
raster_order
|
||||
};
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
@@ -146,10 +111,10 @@ public:
|
||||
// like blockIdx and gridDim, with __CUDA_ARCH__.
|
||||
#if defined(__CUDA_ARCH__)
|
||||
if (params_.raster_order_ == RasterOrder::AlongN) {
|
||||
current_work_linear_idx_ = static_cast<uint64_t>(int(blockIdx.x) + (int(blockIdx.y) * int(gridDim.x)));
|
||||
current_work_linear_idx_ = uint64_t(blockIdx.x) + uint64_t(blockIdx.y) * uint64_t(gridDim.x);
|
||||
}
|
||||
else {
|
||||
current_work_linear_idx_ = static_cast<uint64_t>((int(blockIdx.x) * int(gridDim.y)) + int(blockIdx.y));
|
||||
current_work_linear_idx_ = uint64_t(blockIdx.x) * uint64_t(gridDim.y) + uint64_t(blockIdx.y);
|
||||
}
|
||||
#else
|
||||
CUTLASS_ASSERT(false && "This line should never be reached");
|
||||
@@ -187,7 +152,7 @@ public:
|
||||
// MSVC requires protecting use of CUDA-specific nonstandard syntax,
|
||||
// like blockIdx and gridDim, with __CUDA_ARCH__.
|
||||
#if defined(__CUDA_ARCH__)
|
||||
current_work_linear_idx_ += static_cast<uint64_t>(int(gridDim.x) * int(gridDim.y) * int(gridDim.z)) * advance_count;
|
||||
current_work_linear_idx_ += uint64_t(gridDim.x) * uint64_t(gridDim.y) * uint64_t(gridDim.z) * uint64_t(advance_count);
|
||||
#else
|
||||
CUTLASS_ASSERT(false && "This line should never be reached");
|
||||
#endif
|
||||
@@ -246,35 +211,14 @@ public:
|
||||
CUTLASS_HOST_DEVICE static
|
||||
dim3
|
||||
get_tiled_cta_shape_mnl(ProblemShapeMNKL problem_shape_mnkl, BlockShape cta_shape, ClusterShape cluster_shape) {
|
||||
// Across M and N is our Cluster tile, so we must round up the blocks to the nearest whole number of Cluster tiles
|
||||
auto cta_m = cute::size(cute::ceil_div(cute::shape<0>(problem_shape_mnkl), cute::shape<0>(cta_shape)));
|
||||
auto cta_n = cute::size(cute::ceil_div(cute::shape<1>(problem_shape_mnkl), cute::shape<1>(cta_shape)));
|
||||
|
||||
// Round up to nearest multiple of cluster dim along each mode
|
||||
int problem_blocks_m = round_up(cta_m, cute::size<0>(cluster_shape));
|
||||
int problem_blocks_n = round_up(cta_n, cute::size<1>(cluster_shape));
|
||||
|
||||
// Cluster tile does not span the batch mode, so no extra rounding up required for it
|
||||
int problem_blocks_l = int(cute::size<3>(problem_shape_mnkl));
|
||||
return {uint32_t(problem_blocks_m), uint32_t(problem_blocks_n), uint32_t(problem_blocks_l)};
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int32_t
|
||||
get_log_swizzle_size(int problem_ctas_m, int problem_ctas_n, int max_swizzle_size) {
|
||||
int min_cta_dim = min(problem_ctas_m, problem_ctas_n);
|
||||
if (max_swizzle_size >= 8 && min_cta_dim >= 6) {
|
||||
return 3;
|
||||
}
|
||||
else if (max_swizzle_size >= 4 && min_cta_dim >= 3) {
|
||||
return 2;
|
||||
}
|
||||
else if (max_swizzle_size >= 2 && min_cta_dim >= 2) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
return Params::get_tiled_cta_shape_mnl(
|
||||
to_gemm_coord(problem_shape_mnkl),
|
||||
to_gemm_coord(cluster_shape),
|
||||
cta_m, cta_n
|
||||
);
|
||||
}
|
||||
|
||||
// Given the inputs, computes the physical grid we should launch.
|
||||
@@ -289,111 +233,17 @@ public:
|
||||
Arguments arguments,
|
||||
bool truncate_by_problem_size=true) {
|
||||
|
||||
int const sm_count = hw_info.sm_count;
|
||||
CUTLASS_TRACE_HOST("get_grid_shape(): Persistent schedule grid plan using SM count = " << sm_count);
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape_mnk, cute::Int<1>{});
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, cta_shape, cluster_shape);
|
||||
|
||||
// Compute the total number of output tiles our problem has
|
||||
auto problem_shape_MNKL = cute::append<4>(problem_shape_mnk, cute::Int<1>{});
|
||||
auto [problem_blocks_m, problem_blocks_n, problem_blocks_l] =
|
||||
get_tiled_cta_shape_mnl(problem_shape_MNKL, cta_shape, cluster_shape);
|
||||
|
||||
// Round up to nearest multiple of swizzle_size along each mode
|
||||
auto swizzle_size = 1 << get_log_swizzle_size(problem_blocks_m, problem_blocks_n, arguments.max_swizzle_size);
|
||||
problem_blocks_m = round_up(problem_blocks_m, swizzle_size * cute::size<0>(cluster_shape));
|
||||
problem_blocks_n = round_up(problem_blocks_n, swizzle_size * cute::size<1>(cluster_shape));
|
||||
|
||||
int problem_blocks_total = problem_blocks_m * problem_blocks_n * problem_blocks_l;
|
||||
|
||||
RasterOrder raster_order;
|
||||
raster_order = get_rasterization_order(problem_shape_mnk, cta_shape);
|
||||
dim3 launch_grid;
|
||||
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
launch_grid = dim3(cute::size<0>(cluster_shape), 1, 1);
|
||||
}
|
||||
else {
|
||||
launch_grid = dim3(1, cute::size<1>(cluster_shape), 1);
|
||||
}
|
||||
|
||||
auto possibly_truncate = [&](int x, int y) {
|
||||
if (truncate_by_problem_size) {
|
||||
return std::min(x, y);
|
||||
}
|
||||
else {
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
// The else path is generic, however, we can avoid some divs if we know cluster size is 1
|
||||
if constexpr (size(cluster_shape) == 1) {
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
launch_grid.y = possibly_truncate(sm_count, problem_blocks_total);
|
||||
}
|
||||
else {
|
||||
launch_grid.x = possibly_truncate(sm_count, problem_blocks_total);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Optimal grid size calculation is based on
|
||||
* GH100: 8 GPCs, 72 TPCs (9 TPCs/GPC), 2 SMs/TPC, 144 SMs per full GPU
|
||||
* Hence, maximum SMs per GPC = 18
|
||||
*/
|
||||
constexpr int max_sm_per_gpc = 18;
|
||||
// Provided SM count could possibly be less than the assumed maximum SMs per GPC
|
||||
int const min_num_gpc = sm_count < max_sm_per_gpc ? 1 : sm_count / max_sm_per_gpc;
|
||||
int const max_cta_occupancy_per_gpc = max_sm_per_gpc - (max_sm_per_gpc % size(cluster_shape));
|
||||
int cta_per_device = min_num_gpc * max_cta_occupancy_per_gpc;
|
||||
|
||||
// The calculation below allows for larger grid size launch for different GPUs.
|
||||
int const num_gpc_residual = sm_count < max_sm_per_gpc ? 0 : sm_count % max_sm_per_gpc;
|
||||
int const max_cta_occupancy_per_residual_gpc = num_gpc_residual - (num_gpc_residual % size(cluster_shape));
|
||||
cta_per_device += max_cta_occupancy_per_residual_gpc;
|
||||
|
||||
cta_per_device = sm_count < cta_per_device ? sm_count : cta_per_device;
|
||||
|
||||
if (raster_order == RasterOrder::AlongN) {
|
||||
launch_grid.y = possibly_truncate(
|
||||
cta_per_device / cute::size<0>(cluster_shape),
|
||||
problem_blocks_total / cute::size<0>(cluster_shape));
|
||||
}
|
||||
else {
|
||||
launch_grid.x = possibly_truncate(
|
||||
cta_per_device / cute::size<1>(cluster_shape),
|
||||
problem_blocks_total / cute::size<1>(cluster_shape));
|
||||
}
|
||||
}
|
||||
return launch_grid;
|
||||
}
|
||||
|
||||
template <class ProblemShapeMNKL, class BlockShape>
|
||||
CUTLASS_HOST_DEVICE static RasterOrder get_rasterization_order(ProblemShapeMNKL problem_shape_mnkl, BlockShape cta_shape) {
|
||||
auto tiles_m = cute::size(cute::ceil_div(cute::shape<0>(problem_shape_mnkl), cute::shape<0>(cta_shape)));
|
||||
auto tiles_n = cute::size(cute::ceil_div(cute::shape<1>(problem_shape_mnkl), cute::shape<1>(cta_shape)));
|
||||
|
||||
if (tiles_n > tiles_m) {
|
||||
return RasterOrder::AlongM;
|
||||
}
|
||||
|
||||
return RasterOrder::AlongN;
|
||||
}
|
||||
|
||||
// Splits an input tensor with MxK according to the splitting configuration specified by work_tile_info.
|
||||
// Since the basic tile scheduler does not split output tiles, this method is a no-op.
|
||||
template<class Engine, class Layout>
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
split_MK(cute::Tensor<Engine, Layout> const& tensor, WorkTileInfo const&) {
|
||||
return tensor;
|
||||
}
|
||||
|
||||
// Splits an input tensor with NxK tiles according to the splitting configuration specified by work_tile_info.
|
||||
// Since the basic tile scheduler does not split output tiles, this method is a no-op.
|
||||
template<class Engine, class Layout>
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
split_NK(cute::Tensor<Engine, Layout> const& tensor, WorkTileInfo const&) {
|
||||
return tensor;
|
||||
return Params::get_grid_shape(
|
||||
problem_blocks,
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
arguments.max_swizzle_size,
|
||||
arguments.raster_order,
|
||||
/* truncate_by_problem_size = */true
|
||||
);
|
||||
}
|
||||
|
||||
// Returns whether the block assigned this work should compute the epilogue for the corresponding
|
||||
@@ -441,6 +291,13 @@ public:
|
||||
// space of the output tile assigned to the work unit.
|
||||
return cute::size(cute::ceil_div(cute::get<2>(problem_shape), cute::get<2>(tile_shape)));
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
static uint32_t
|
||||
get_work_k_tile_start(WorkTileInfo const&) {
|
||||
// All work units returned by this scheduler start from K tile 0
|
||||
return 0u;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cutlass::gemm::kernel::detail
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include "cutlass/barrier.h"
|
||||
#include "cutlass/block_striped.h"
|
||||
#include "cutlass/fast_math.h"
|
||||
#include "cutlass/workspace.hpp"
|
||||
#include "cutlass/gemm/kernel/sm90_tile_scheduler.hpp"
|
||||
#include "cutlass/kernel_hardware_info.hpp"
|
||||
#include "cute/layout.hpp"
|
||||
@@ -54,22 +53,21 @@ class PersistentTileSchedulerSm90StreamK {
|
||||
|
||||
private:
|
||||
using UnderlyingScheduler = PersistentTileSchedulerSm90;
|
||||
public:
|
||||
using RasterOrder = UnderlyingScheduler::RasterOrder;
|
||||
|
||||
private:
|
||||
using UnderlyingArguments = typename UnderlyingScheduler::Arguments;
|
||||
using UnderlyingParams = typename UnderlyingScheduler::Params;
|
||||
|
||||
uint64_t current_work_linear_idx_ = 0;
|
||||
|
||||
// Minimum number of k iterations that can be assigned to a stream-K unit
|
||||
static constexpr uint32_t min_iters_per_sk_unit_ = 2;
|
||||
public:
|
||||
|
||||
using RasterOrder = UnderlyingScheduler::RasterOrder;
|
||||
using RasterOrderOptions = UnderlyingScheduler::RasterOrderOptions;
|
||||
|
||||
// Use a dummy barrier manager to simply get the type used to store the barrier
|
||||
using BarrierType = typename NamedBarrierManager<1>::T;
|
||||
|
||||
public:
|
||||
|
||||
struct WorkTileInfo {
|
||||
int32_t M_idx = 0;
|
||||
int32_t N_idx = 0;
|
||||
@@ -91,27 +89,32 @@ public:
|
||||
bool is_final_split = true;
|
||||
};
|
||||
|
||||
using Params = PersistentTileSchedulerSm90StreamKParams;
|
||||
using ReductionMode = Params::ReductionMode;
|
||||
|
||||
struct Arguments {
|
||||
|
||||
Arguments() = default;
|
||||
Arguments(Arguments const&) = default;
|
||||
Arguments(Arguments&&) = default;
|
||||
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments&
|
||||
Arguments&
|
||||
operator=(Arguments const& args) {
|
||||
splits = args.splits;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments&
|
||||
operator=(Arguments&& args) noexcept {
|
||||
splits = args.splits;
|
||||
raster_order = args.raster_order;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments&
|
||||
operator=(Arguments&& args) noexcept {
|
||||
splits = args.splits;
|
||||
raster_order = args.raster_order;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
Arguments(int splits_) : splits(splits_) {}
|
||||
|
||||
// The splitting factor to be used in a split-K decomposition of the problem.
|
||||
@@ -119,48 +122,8 @@ public:
|
||||
// is bypassed in favor of a split-K decomposition.
|
||||
int splits = 1;
|
||||
const int max_swizzle_size = 1;
|
||||
};
|
||||
|
||||
struct Params {
|
||||
FastDivmodU64 divmod_cluster_shape_major_{};
|
||||
FastDivmodU64 divmod_cluster_shape_minor_{};
|
||||
FastDivmodU64 divmod_batch_{};
|
||||
FastDivmodU64 divmod_k_{};
|
||||
FastDivmodU64 divmod_cluster_blk_major_{};
|
||||
|
||||
int32_t log_swizzle_size_ = 0;
|
||||
|
||||
|
||||
uint64_t units_per_problem_ = 0;
|
||||
RasterOrder raster_order_ = RasterOrder::AlongN;
|
||||
ClusterShape cluster_shape_{};
|
||||
|
||||
// The splitting factor to be used in a split-K decomposition of the problem.
|
||||
// If this is set to a value greater than 1, stream-K decomposition logic
|
||||
// is bypassed in favor of a split-K decomposition.
|
||||
uint32_t splits_ = 1;
|
||||
|
||||
// Number of tiled k iterations required to compute a single output tile.
|
||||
uint32_t k_tiles_per_output_tile_ = 0;
|
||||
|
||||
// Number of stream-K or split-K work units that compute an extra k iteration.
|
||||
// This is done to handle residuals in dividing up the k iteration space.
|
||||
// For stream-K, since the actual assignment of work to stream-K units will be done
|
||||
// at the granularity of a cluster, we store only the number of big clusters.
|
||||
uint32_t big_units_ = 0;
|
||||
|
||||
// Workspace for holding partial accumulators to be reduced across stream-K/split-K units
|
||||
void* reduction_workspace_ = nullptr;
|
||||
|
||||
// Number of tiles covered by stream-K work units
|
||||
uint32_t sk_tiles_ = 0;
|
||||
|
||||
// Number of work units computing stream-K tiles
|
||||
uint32_t sk_units_ = 0;
|
||||
|
||||
// Number of tiled k iterations computed by each stream-K work unit. This
|
||||
// can potentially cover more than one output tile.
|
||||
uint32_t k_tiles_per_sk_unit_ = 0;
|
||||
RasterOrderOptions raster_order = RasterOrderOptions::Heuristic;
|
||||
ReductionMode reduction_mode = ReductionMode::Deterministic;
|
||||
};
|
||||
|
||||
// Sink scheduler params as a member
|
||||
@@ -173,7 +136,7 @@ public:
|
||||
template <class ProblemShape>
|
||||
static Params
|
||||
to_underlying_arguments(
|
||||
ProblemShape problem_shape_mnkl,
|
||||
ProblemShape problem_shape,
|
||||
TileShape tile_shape,
|
||||
ClusterShape cluster_shape,
|
||||
KernelHardwareInfo const& hw_info,
|
||||
@@ -183,143 +146,23 @@ public:
|
||||
static_assert(cute::is_static<TileShape>::value);
|
||||
static_assert(cute::is_static<ClusterShape>::value);
|
||||
|
||||
// Round up to nearest multiple of cluster dim along each mode
|
||||
auto [problem_blocks_m, problem_blocks_n, problem_blocks_l] = get_tiled_cta_shape_mnl(
|
||||
problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, cute::Int<1>{});
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));
|
||||
|
||||
uint64_t output_tiles = problem_blocks_m * problem_blocks_n * problem_blocks_l;
|
||||
|
||||
// Number of k tile iterations in each output tile
|
||||
uint32_t k_tiles_per_output_tile = (cute::size<2>(problem_shape_mnkl) + cute::size<2>(tile_shape) - 1) /
|
||||
cute::size<2>(tile_shape);
|
||||
|
||||
UnderlyingArguments underlying_args;
|
||||
underlying_args.max_swizzle_size = 1;
|
||||
UnderlyingParams underlying_params = UnderlyingScheduler::to_underlying_arguments(
|
||||
problem_shape_mnkl, tile_shape, cluster_shape, hw_info, underlying_args, workspace);
|
||||
|
||||
void* reduction_workspace = nullptr;
|
||||
|
||||
if (workspace != nullptr) {
|
||||
// Reduction workspace is at the beginning of the workspace. Lock workspace follows.
|
||||
reduction_workspace = workspace;
|
||||
}
|
||||
|
||||
if (args.splits > 1) {
|
||||
// Short circuit to basic split-K decomposition
|
||||
|
||||
// Don't split by more than the available number of SMs
|
||||
auto splits = args.splits > hw_info.sm_count ? hw_info.sm_count : args.splits;
|
||||
|
||||
// Don't split by more than the K tile iterations
|
||||
//
|
||||
// splits is almost certainly nonnegative here (e.g., hw_info.sm_count,
|
||||
// despite being an int, is a count), so it can safely be converted to unsigned
|
||||
// in the comparison to avoid a signed-unsigned comparison warning-as-error.
|
||||
splits = static_cast<decltype(k_tiles_per_output_tile)>(splits) > k_tiles_per_output_tile ? k_tiles_per_output_tile : splits;
|
||||
|
||||
return get_params_basic(
|
||||
underlying_params, problem_blocks_m, problem_blocks_n, problem_blocks_l, cluster_shape,
|
||||
splits, k_tiles_per_output_tile, reduction_workspace);
|
||||
}
|
||||
|
||||
// Calculate the maximum number of blocks from clusters of shape cluster_shape that we
|
||||
// can fit within sm_count SMs.
|
||||
dim3 grid = get_grid_shape(problem_shape_mnkl, tile_shape, cluster_shape, hw_info, args);
|
||||
uint64_t ctas_per_wave = grid.x * grid.y;
|
||||
|
||||
// The number of output tiles to be computed in stream-K and data-parallel fashion, respectively.
|
||||
uint32_t sk_tiles = get_num_sk_tiles(output_tiles, ctas_per_wave, k_tiles_per_output_tile);
|
||||
uint64_t dp_tiles = output_tiles - sk_tiles;
|
||||
|
||||
// Calculate the number of work units covering the data-parallel and stream-K tiles.
|
||||
// A "work unit" is a single index in the linearized ID space used by the scheduler.
|
||||
// We distinguish it from a "block," which is typically tied to a hardware unit
|
||||
// (e.g., the callers into this scheduler will be persistent thread blocks).
|
||||
// A work unit can encompass multiple output tiles worth of work (as will be the
|
||||
// case for stream-K blocks).
|
||||
// Since splitting is not required for data-parallel tiles, only one data-parallel unit
|
||||
// is needed per data-parallel tile.
|
||||
uint64_t dp_units = dp_tiles;
|
||||
|
||||
// Number of k iterations computed by the stream-K units as a whole
|
||||
uint64_t k_tiles_sk_total = k_tiles_per_output_tile * sk_tiles;
|
||||
|
||||
// If there are stream-K tiles to compute and a sufficiently large number of k iterations
|
||||
// across them, they will be covered by a single wave of persistent threadblocks. Thus, there
|
||||
// will be as many work units as there are threadblocks in a single wave.
|
||||
//
|
||||
// When the total k iterations across stream-K tiles is too small to justify distributing
|
||||
// across an entire wave of blocks, we instead distribute the iterations over a smaller
|
||||
// set of blocks.
|
||||
|
||||
// Calculate the number of stream-K units that would be needed if each stream-K unit
|
||||
// computed the minimum allowable k iterations. Truncate this to be in units of clusters.
|
||||
uint64_t min_sized_sk_units = (k_tiles_sk_total / min_iters_per_sk_unit_);
|
||||
min_sized_sk_units = (min_sized_sk_units / cute::size(cluster_shape)) * cute::size(cluster_shape);
|
||||
|
||||
uint64_t sk_units = min(ctas_per_wave, min_sized_sk_units);
|
||||
|
||||
if (sk_units == 0) {
|
||||
// Short circuit to basic data-parallel decomposition
|
||||
return get_params_basic(
|
||||
underlying_params, problem_blocks_m, problem_blocks_n, problem_blocks_l, cluster_shape,
|
||||
1, k_tiles_per_output_tile, reduction_workspace);
|
||||
}
|
||||
|
||||
// If the number of stream-K units is a multiple of the number of stream-K tiles, then
|
||||
// the problem can leverage a basic split-K decomposition for the stream-K tiles.
|
||||
if (sk_tiles < sk_units && sk_units % sk_tiles == 0) {
|
||||
// Short circuit to basic split-K decomposition
|
||||
uint32_t sk_splits = static_cast<uint32_t>(sk_units / sk_tiles);
|
||||
return get_params_basic(
|
||||
underlying_params, problem_blocks_m, problem_blocks_n, problem_blocks_l, cluster_shape,
|
||||
sk_splits, k_tiles_per_output_tile, reduction_workspace);
|
||||
}
|
||||
|
||||
// Number of k iterations computed per stream-K units
|
||||
uint64_t k_tiles_per_sk_unit = k_tiles_sk_total / sk_units;
|
||||
|
||||
// Number of stream-K units that need to compute extra iterations in order to cover
|
||||
// the residual k iterations. This assumes that each such unit computes one additional
|
||||
// iteration.
|
||||
uint64_t sk_big_units = k_tiles_sk_total - (k_tiles_per_sk_unit * sk_units);
|
||||
|
||||
// The division below is guaranteed to be exact because sk_big_units is guaranteed
|
||||
// to be a multiple of cluster_size (cute::size(cluster_shape)). This is useful because
|
||||
// it allows us to use a block's linearized cluster ID to determine whether it is
|
||||
// a big block. The reasoning behind this guarnatee is explained as follows:
|
||||
// sk_big_units = k_tiles_sk_total - (k_tiles_per_sk_unit * sk_units);
|
||||
//
|
||||
// - k_tiles_sk_total is a multiple of cluster_size because it is the product
|
||||
// of number of tail tiles and the number of k iterations per tile. Because
|
||||
// both the number of output tiles and number of available SMs are rounded
|
||||
// to be multiples of cluster shape, the number of tail tiles
|
||||
// (output_tiles % avail_sms) is a multpile of cluster_size.
|
||||
//
|
||||
// - sk_units is a multiple of cluster_size because it is either blocks_per_wave
|
||||
// or 0, and blocks_per_wave is a multiple of the cluster_size due to the grid-planning
|
||||
// logic rounding to multiples of cluster dimensions
|
||||
uint64_t sk_big_units_per_cluster = sk_big_units / cute::size(cluster_shape);
|
||||
|
||||
return {
|
||||
underlying_params.divmod_cluster_shape_major_,
|
||||
underlying_params.divmod_cluster_shape_minor_,
|
||||
underlying_params.divmod_batch_,
|
||||
FastDivmodU64(problem_blocks_m * problem_blocks_n), // Static k-splitting divmod. Unused for stream-K.
|
||||
underlying_params.divmod_cluster_blk_major_,
|
||||
underlying_params.log_swizzle_size_,
|
||||
static_cast<uint32_t>(dp_units + sk_units),
|
||||
underlying_params.raster_order_,
|
||||
cluster_shape,
|
||||
1, // Static k-splitting factor. Unused for stream-K.
|
||||
k_tiles_per_output_tile,
|
||||
static_cast<uint32_t>(sk_big_units_per_cluster),
|
||||
reduction_workspace,
|
||||
sk_tiles,
|
||||
static_cast<uint32_t>(sk_units),
|
||||
static_cast<uint32_t>(k_tiles_per_sk_unit)
|
||||
};
|
||||
Params params;
|
||||
params.initialize(
|
||||
problem_blocks,
|
||||
k_tile_per_output_tile,
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
args.splits,
|
||||
args.max_swizzle_size,
|
||||
args.raster_order,
|
||||
args.reduction_mode,
|
||||
workspace
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
CUTLASS_HOST_DEVICE
|
||||
@@ -328,10 +171,10 @@ public:
|
||||
CUTLASS_HOST_DEVICE
|
||||
PersistentTileSchedulerSm90StreamK(Params const& params_) : scheduler_params(params_) {
|
||||
if (params_.raster_order_ == RasterOrder::AlongN) {
|
||||
current_work_linear_idx_ = static_cast<uint64_t>(int(blockIdx.x) + (int(blockIdx.y) * int(gridDim.x)));
|
||||
current_work_linear_idx_ = uint64_t(blockIdx.x) + uint64_t(blockIdx.y) * uint64_t(gridDim.x);
|
||||
}
|
||||
else {
|
||||
current_work_linear_idx_ = static_cast<uint64_t>((int(blockIdx.x) * int(gridDim.y)) + int(blockIdx.y));
|
||||
current_work_linear_idx_ = uint64_t(blockIdx.x) * uint64_t(gridDim.y) + uint64_t(blockIdx.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +240,7 @@ public:
|
||||
CUTLASS_DEVICE
|
||||
void
|
||||
advance_to_next_work(uint32_t advance_count = 1) {
|
||||
current_work_linear_idx_ += static_cast<uint64_t>(int(gridDim.x) * int(gridDim.y) * int(gridDim.z)) * advance_count;
|
||||
current_work_linear_idx_ += uint64_t(gridDim.x) * uint64_t(gridDim.y) * uint64_t(gridDim.z) * uint64_t(advance_count);
|
||||
}
|
||||
|
||||
// Given the inputs, computes the total number of output blocks this problem will compute over
|
||||
@@ -420,17 +263,16 @@ public:
|
||||
KernelHardwareInfo hw_info,
|
||||
Arguments arguments) {
|
||||
|
||||
UnderlyingArguments underlying_args;
|
||||
underlying_args.max_swizzle_size = 1;
|
||||
// Call into the underlying get_grid_shape method, but do not allow the grid shape returned
|
||||
// to be truncated based on the number of output tiles in the problem.
|
||||
return UnderlyingScheduler::get_grid_shape(
|
||||
problem_shape,
|
||||
tile_shape,
|
||||
cluster_shape,
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, cute::Int<1>{});
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
|
||||
return Params::get_grid_shape(
|
||||
problem_blocks,
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
underlying_args,
|
||||
/*truncate_by_problem_size=*/false);
|
||||
arguments.max_swizzle_size,
|
||||
arguments.raster_order
|
||||
);
|
||||
}
|
||||
|
||||
// Returns whether fixup is needed for `work_tile_info`.
|
||||
@@ -501,7 +343,8 @@ public:
|
||||
// note that, in the split-K case, the units_per_problem_ member of Params will be
|
||||
// the total number of output tiles multiplied by the number of splits.
|
||||
auto reduction_tiles = params.splits_ > 1 ? (params.units_per_problem_ / params.splits_) : params.sk_tiles_;
|
||||
auto reduction_workspace_size = get_reduction_workspace_size<ElementAccumulator>(reduction_tiles);
|
||||
auto reduction_workspace_size = Params::get_reduction_workspace_size(
|
||||
reduction_tiles, to_gemm_coord(TileShape{}), sizeof_bits<ElementAccumulator>::value);
|
||||
BarrierType* lock_workspace = reinterpret_cast<BarrierType*>(
|
||||
reinterpret_cast<uint8_t*>(params.reduction_workspace_) + reduction_workspace_size);
|
||||
|
||||
@@ -511,8 +354,14 @@ public:
|
||||
BlockStripedReduceT::store(reduction_workspace_array, *accumulator_array, barrier_group_thread_idx);
|
||||
}
|
||||
else {
|
||||
// Wait until the preceding split added its accumulators
|
||||
BarrierManager::wait_eq(barrier_idx, lock_workspace, barrier_group_thread_idx, lock_idx, work_tile_info.K_idx);
|
||||
if (params.reduction_mode_ == ReductionMode::Deterministic) {
|
||||
// Wait until the preceding split added its accumulators
|
||||
BarrierManager::wait_eq(barrier_idx, lock_workspace, barrier_group_thread_idx, lock_idx, work_tile_info.K_idx);
|
||||
}
|
||||
else {
|
||||
// Wait unitl the first split has stored its accumulators
|
||||
BarrierManager::wait_lt(barrier_idx, lock_workspace, barrier_group_thread_idx, lock_idx, 1);
|
||||
}
|
||||
|
||||
// Perform reduction in workspace
|
||||
BlockStripedReduceT::reduce(reduction_workspace_array, *accumulator_array, barrier_group_thread_idx);
|
||||
@@ -531,22 +380,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Splits an input tensor with MxK according to the splitting configuration specified by work_tile_info
|
||||
template<class Engine, class Layout>
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
split_MK(cute::Tensor<Engine, Layout> const& tensor, WorkTileInfo const& work_tile_info) {
|
||||
return split<Engine, Layout, 0>(tensor, work_tile_info);
|
||||
}
|
||||
|
||||
// Splits an input tensor with NxK tiles according to the splitting configuration specified by work_tile_info
|
||||
template<class Engine, class Layout>
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
split_NK(cute::Tensor<Engine, Layout> const& tensor, WorkTileInfo const& work_tile_info) {
|
||||
return split<Engine, Layout, 1>(tensor, work_tile_info);
|
||||
}
|
||||
|
||||
// Returns whether the block assigned this work should compute the epilogue for the corresponding
|
||||
// output tile. For the case of stream-K, this should only occur if the work is marked as the final split.
|
||||
CUTLASS_HOST_DEVICE
|
||||
@@ -564,14 +397,14 @@ public:
|
||||
if (params.raster_order_ == RasterOrder::AlongN) {
|
||||
return
|
||||
(tiles_mn * work_tile_info.L_idx) +
|
||||
(params.divmod_cluster_shape_major_.divisor *
|
||||
(params.divmod_cluster_shape_major_.divisor *
|
||||
params.divmod_cluster_blk_major_.divisor * work_tile_info.M_idx) +
|
||||
work_tile_info.N_idx;
|
||||
}
|
||||
else {
|
||||
return
|
||||
(tiles_mn * work_tile_info.L_idx) +
|
||||
(params.divmod_cluster_shape_major_.divisor *
|
||||
(params.divmod_cluster_shape_major_.divisor *
|
||||
params.divmod_cluster_blk_major_.divisor * work_tile_info.N_idx) +
|
||||
work_tile_info.M_idx;
|
||||
}
|
||||
@@ -582,16 +415,16 @@ public:
|
||||
uint64_t cta_per_grid_dim;
|
||||
uint64_t cluster_dim_idx;
|
||||
if (params.raster_order_ == RasterOrder::AlongN) {
|
||||
uint64_t block_idx_m = (work_tile_info.M_idx - cta_m_in_cluster) / cute::size<0>(params.cluster_shape_);
|
||||
uint64_t block_idx_m = (work_tile_info.M_idx - cta_m_in_cluster) / params.divmod_cluster_shape_minor_.divisor;
|
||||
uint64_t block_idx_n = work_tile_info.N_idx;
|
||||
cta_per_grid_dim = (params.divmod_cluster_shape_major_.divisor *
|
||||
cta_per_grid_dim = (params.divmod_cluster_shape_major_.divisor *
|
||||
params.divmod_cluster_blk_major_.divisor * block_idx_m) + block_idx_n;
|
||||
cluster_dim_idx = cta_m_in_cluster;
|
||||
}
|
||||
else {
|
||||
uint64_t block_idx_m = work_tile_info.M_idx;
|
||||
uint64_t block_idx_n = (work_tile_info.N_idx - cta_n_in_cluster) / cute::size<1>(params.cluster_shape_);
|
||||
cta_per_grid_dim = (params.divmod_cluster_shape_major_.divisor *
|
||||
uint64_t block_idx_n = (work_tile_info.N_idx - cta_n_in_cluster) / params.divmod_cluster_shape_minor_.divisor;
|
||||
cta_per_grid_dim = (params.divmod_cluster_shape_major_.divisor *
|
||||
params.divmod_cluster_blk_major_.divisor * block_idx_n) + block_idx_m;
|
||||
cluster_dim_idx = cta_n_in_cluster;
|
||||
}
|
||||
@@ -609,13 +442,27 @@ public:
|
||||
KernelHardwareInfo const& hw_info,
|
||||
uint32_t mma_warp_groups) {
|
||||
|
||||
int barrier_workspace_size = 0;
|
||||
int reduction_workspace_size = 0;
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
|
||||
|
||||
get_workspace_component_sizes<ProblemShape, ElementAccumulator>(
|
||||
args, problem_shape, barrier_workspace_size, reduction_workspace_size, hw_info, mma_warp_groups);
|
||||
ClusterShape cluster_shape;
|
||||
TileShape tile_shape;
|
||||
|
||||
return barrier_workspace_size + reduction_workspace_size;
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));
|
||||
|
||||
return Params::get_workspace_size(
|
||||
problem_blocks,
|
||||
k_tile_per_output_tile,
|
||||
to_gemm_coord(tile_shape),
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
args.splits,
|
||||
args.max_swizzle_size,
|
||||
args.raster_order,
|
||||
mma_warp_groups,
|
||||
sizeof_bits<BarrierType>::value,
|
||||
sizeof_bits<ElementAccumulator>::value
|
||||
);
|
||||
}
|
||||
|
||||
template <class ProblemShape, class ElementAccumulator>
|
||||
@@ -628,26 +475,29 @@ public:
|
||||
KernelHardwareInfo const& hw_info,
|
||||
uint32_t mma_warp_groups) {
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
int barrier_workspace_size = 0;
|
||||
int reduction_workspace_size = 0;
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
|
||||
|
||||
get_workspace_component_sizes<ProblemShape, ElementAccumulator>(
|
||||
args, problem_shape, barrier_workspace_size, reduction_workspace_size, hw_info, mma_warp_groups);
|
||||
ClusterShape cluster_shape;
|
||||
TileShape tile_shape;
|
||||
|
||||
if (barrier_workspace_size > 0) {
|
||||
if (workspace == nullptr) {
|
||||
return Status::kErrorWorkspaceNull;
|
||||
}
|
||||
dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
|
||||
uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));
|
||||
|
||||
// Only the barrier workspace needs to be cleared for stream-K.
|
||||
// Barrier workspace follows reduction workspace.
|
||||
uint8_t* barrier_workspace = reinterpret_cast<uint8_t*>(workspace) + reduction_workspace_size;
|
||||
return zero_workspace(static_cast<void*>(barrier_workspace), barrier_workspace_size, stream);
|
||||
}
|
||||
|
||||
return Status::kSuccess;
|
||||
#endif
|
||||
return Params::initialize_workspace(
|
||||
workspace,
|
||||
stream,
|
||||
problem_blocks,
|
||||
k_tile_per_output_tile,
|
||||
to_gemm_coord(tile_shape),
|
||||
to_gemm_coord(cluster_shape),
|
||||
hw_info,
|
||||
args.splits,
|
||||
args.max_swizzle_size,
|
||||
args.raster_order,
|
||||
mma_warp_groups,
|
||||
sizeof_bits<BarrierType>::value,
|
||||
sizeof_bits<ElementAccumulator>::value
|
||||
);
|
||||
}
|
||||
|
||||
template <class ProblemShape>
|
||||
@@ -657,162 +507,10 @@ public:
|
||||
return work_tile_info.k_tile_count;
|
||||
}
|
||||
|
||||
private:
|
||||
// Splits a tensor using the splitting configuration specified by work_tile_info using
|
||||
// a MN shape detemined by TileDim0.
|
||||
template <class Engine, class Layout, int TileDim0>
|
||||
CUTLASS_DEVICE
|
||||
static auto
|
||||
split(cute::Tensor<Engine, Layout> const& tensor, WorkTileInfo const& work_tile_info) {
|
||||
using namespace cute;
|
||||
|
||||
// Divide input tensor into `splits` chunks along the k dimension
|
||||
auto div_shape = make_shape(size<TileDim0>(TileShape{}), size<2>(TileShape{}), work_tile_info.splits);
|
||||
auto split = zipped_divide(tensor, div_shape);
|
||||
|
||||
// Index into the split tensor at the work tile's split index
|
||||
auto indexed = split(make_coord(make_coord(_, _, work_tile_info.K_idx), make_coord(0, 0, _)));
|
||||
|
||||
// Construct a layout for the indexed tensor. The main purpose of this new layout is to
|
||||
// override the k extent to support cases in which the split computes a number of iterations
|
||||
// not equal to total_k_tiles / splits. A common example of this is in stream-K is when a
|
||||
// unit computes the final 20 of the total 32 k iterations of the output tile. In this case,
|
||||
// set splits = 32 and the split index (K_idx) to 11. The zipped divide above results in each
|
||||
// of the splits computing only one k iteration.
|
||||
auto overridden_shape = make_shape(size<0>(indexed.layout()), size<1>(indexed.layout()), work_tile_info.k_tile_count);
|
||||
auto layout = make_layout(overridden_shape, tensor.stride());
|
||||
|
||||
return make_tensor(indexed.data(), layout);
|
||||
}
|
||||
|
||||
// Returns the number of stream-K tiles that will be computed amongst `output_tiles` total
|
||||
// output tiles on a device with `ctas_per_wave` CTAs in each wave.
|
||||
CUTLASS_HOST_DEVICE
|
||||
static uint32_t
|
||||
get_num_sk_tiles(uint64_t output_tiles, uint64_t ctas_per_wave, uint32_t k_tiles_per_output_tile) {
|
||||
uint32_t full_waves = static_cast<uint32_t>(output_tiles / ctas_per_wave);
|
||||
uint32_t total_waves = static_cast<uint32_t>((output_tiles + ctas_per_wave - 1) / ctas_per_wave);
|
||||
|
||||
if (full_waves == total_waves || k_tiles_per_output_tile == 1) {
|
||||
// All tiles will be data-parallel tiles if there is either no quantization
|
||||
// or if there is no work to be split.
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// The final wave is not full. Perform some stream-K work.
|
||||
//
|
||||
|
||||
// Rudimentary heuristic: prefer data-parallel decomposition if we have more than
|
||||
// one wave and the tail wave is more than half full. This is subject to change.
|
||||
if (full_waves != 0) {
|
||||
uint64_t tail_tiles = output_tiles - (full_waves * ctas_per_wave);
|
||||
if (tail_tiles >= (ctas_per_wave / 2)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is wave quantization, assign the first two waves worth of tiles to be
|
||||
// covered by stream-K work and the remainder to be data-parallel. Since we know
|
||||
// that full_waves == total_waves - 1 in this case, the number of data-parallel
|
||||
// waves is simply full_waves-1 (unless full_waves == 0).
|
||||
uint32_t dp_waves = full_waves > 0 ? full_waves - 1 : 0;
|
||||
|
||||
uint64_t dp_tiles = dp_waves * ctas_per_wave;
|
||||
return static_cast<uint32_t>(output_tiles - dp_tiles);
|
||||
}
|
||||
|
||||
// Calculates the size of the workspace needed for holding reduction barriers
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int
|
||||
get_barrier_workspace_size(uint64_t num_tiles, uint32_t mma_warp_groups) {
|
||||
auto workspace_bits = num_tiles * mma_warp_groups * sizeof_bits<BarrierType>::value;
|
||||
return bits_to_bytes(static_cast<int>(workspace_bits));
|
||||
}
|
||||
|
||||
// Calculates the size of the workspace needed for holding partial outputs from splits
|
||||
template <class ElementAccumulator>
|
||||
CUTLASS_HOST_DEVICE
|
||||
static int
|
||||
get_reduction_workspace_size(uint64_t num_tiles) {
|
||||
auto output_tile_size = cute::size<0>(TileShape{}) * cute::size<1>(TileShape{});
|
||||
auto workspace_bits = sizeof_bits<ElementAccumulator>::value * output_tile_size * num_tiles;
|
||||
return bits_to_bytes(static_cast<int>(workspace_bits));
|
||||
}
|
||||
|
||||
template <class ProblemShape, class ElementAccumulator>
|
||||
static void
|
||||
get_workspace_component_sizes(
|
||||
Arguments const& args,
|
||||
ProblemShape problem_shape,
|
||||
int& barrier_workspace_size,
|
||||
int& reduction_workspace_size,
|
||||
KernelHardwareInfo const& hw_info,
|
||||
uint32_t mma_warp_groups) {
|
||||
|
||||
// Workspace is needed only for output tiles that will be split. Thus, we first determine the number
|
||||
// of output tiles that will be split, and then calculate the workspace needed to cover these.
|
||||
|
||||
auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
|
||||
|
||||
ClusterShape cluster_shape;
|
||||
auto [problem_blocks_m, problem_blocks_n, problem_blocks_l] = get_tiled_cta_shape_mnl(
|
||||
problem_shape_mnkl, TileShape{}, cluster_shape);
|
||||
uint64_t output_tiles = problem_blocks_m * problem_blocks_n * problem_blocks_l;
|
||||
|
||||
if (args.splits > 1) {
|
||||
// Basic split-K variant requires workspace for all output tiles
|
||||
barrier_workspace_size = get_barrier_workspace_size(output_tiles, mma_warp_groups);
|
||||
reduction_workspace_size = get_reduction_workspace_size<ElementAccumulator>(output_tiles);
|
||||
}
|
||||
else {
|
||||
int sm_count = hw_info.sm_count;
|
||||
if (sm_count <= 0) {
|
||||
CUTLASS_TRACE_HOST(" WARNING: Arguments do not include a valid SM count.\n"
|
||||
" For optimal performance, populate the arguments KernelHardwareInfo struct with the SM count.");
|
||||
sm_count = KernelHardwareInfo::query_device_multiprocessor_count(hw_info.device_id);
|
||||
}
|
||||
|
||||
uint32_t k_tiles_per_output_tile = (cute::size<2>(problem_shape_mnkl) + cute::size<2>(TileShape{}) - 1) /
|
||||
cute::size<2>(TileShape{});
|
||||
|
||||
dim3 grid = get_grid_shape(problem_shape_mnkl, TileShape{}, cluster_shape, {0, sm_count}, args);
|
||||
uint64_t ctas_per_wave = grid.x * grid.y;
|
||||
uint32_t sk_tiles = get_num_sk_tiles(output_tiles, ctas_per_wave, k_tiles_per_output_tile);
|
||||
|
||||
barrier_workspace_size = get_barrier_workspace_size(sk_tiles, mma_warp_groups);
|
||||
reduction_workspace_size = get_reduction_workspace_size<ElementAccumulator>(sk_tiles);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructs parameters for either a basic data-parallel or basic split-K decomposition of the problem
|
||||
static Params
|
||||
get_params_basic(
|
||||
UnderlyingParams const& underlying_params,
|
||||
uint32_t blocks_m,
|
||||
uint32_t blocks_n,
|
||||
uint32_t blocks_l,
|
||||
ClusterShape cluster_shape,
|
||||
uint32_t splits,
|
||||
uint32_t k_tiles_per_output_tile,
|
||||
void* reduction_workspace) {
|
||||
|
||||
uint32_t big_units = k_tiles_per_output_tile % splits;
|
||||
|
||||
return {
|
||||
underlying_params.divmod_cluster_shape_major_,
|
||||
underlying_params.divmod_cluster_shape_minor_,
|
||||
FastDivmodU64(blocks_m * blocks_n * splits),
|
||||
FastDivmodU64(blocks_m * blocks_n),
|
||||
underlying_params.divmod_cluster_blk_major_,
|
||||
underlying_params.log_swizzle_size_,
|
||||
blocks_m * blocks_n * blocks_l * splits,
|
||||
underlying_params.raster_order_,
|
||||
cluster_shape,
|
||||
splits,
|
||||
k_tiles_per_output_tile,
|
||||
big_units,
|
||||
reduction_workspace
|
||||
};
|
||||
get_work_k_tile_start(WorkTileInfo const& work_tile_info) {
|
||||
return work_tile_info.K_idx;
|
||||
}
|
||||
|
||||
// Sets the current stream-K work to compute within work_tile_info. If new_unit is true, work_tile_info
|
||||
@@ -840,7 +538,8 @@ private:
|
||||
//
|
||||
// To do so, we divide up the linearized stream-K units into clusters and share the same K
|
||||
// offsets for work within clusters.
|
||||
auto cluster_linear_work_idx = linear_idx / size(params.cluster_shape_);
|
||||
auto cluster_size = params.divmod_cluster_shape_major_.divisor * params.divmod_cluster_shape_minor_.divisor;
|
||||
auto cluster_linear_work_idx = linear_idx / cluster_size;
|
||||
|
||||
// Determine the starting k iteration computed by this stream-K work unit
|
||||
uint32_t unit_iter_start = params.k_tiles_per_sk_unit_ * cluster_linear_work_idx;
|
||||
@@ -890,16 +589,16 @@ private:
|
||||
uint32_t true_tile_iter_end = true_tile_iter_start + params.k_tiles_per_output_tile_;
|
||||
|
||||
// Bring the linearized tile ID back into the space of tiles, rather than clusters
|
||||
true_tile_id *= size(params.cluster_shape_);
|
||||
true_tile_id *= cluster_size;
|
||||
|
||||
auto [cta_m_in_cluster, cta_n_in_cluster, _] = cute::block_id_in_cluster();
|
||||
|
||||
// The final linearized tile ID is in units of the cluster dimension over which we rasterize.
|
||||
if (params.raster_order_ == RasterOrder::AlongN) {
|
||||
true_tile_id += cta_n_in_cluster * cute::size<0>(params.cluster_shape_);
|
||||
true_tile_id += cta_n_in_cluster * params.divmod_cluster_shape_minor_.divisor;
|
||||
}
|
||||
else {
|
||||
true_tile_id += cta_m_in_cluster * cute::size<1>(params.cluster_shape_);
|
||||
true_tile_id += cta_m_in_cluster * params.divmod_cluster_shape_minor_.divisor;
|
||||
}
|
||||
|
||||
// The unit's starting k iteration in the current tile is either the starting
|
||||
@@ -925,7 +624,7 @@ private:
|
||||
params.divmod_cluster_shape_major_,
|
||||
params.divmod_cluster_shape_minor_,
|
||||
params.divmod_cluster_blk_major_,
|
||||
params.log_swizzle_size_,
|
||||
params.log_swizzle_size_,
|
||||
params.raster_order_);
|
||||
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user