Extend DualGemm: support batched mode + decouple B0/B1 layouts (#790)

* Fix MHA kernel

Summary:

ATT

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:

* Extend DualGemm to support batched mode (#5)

Following the GemmUniversalMode::kBatched implementation, batched mode is added to the DualGemm (under examples/45_dual_gemm). DualGemmMode::kBatched and SplitKSerial are not compatible: Status::kErrorInvalidProblem is returned if both are set.

* Decouple LayoutB0 and LayoutB1 in DualGemm

The DualGemm template assumed the same layout, LayoutB, for both right operand matrices B0 and B1. This is problematic if the layout of the two matrices is different. In particular, this may be the case when one of the matrices is row-major, while the other is a (column) vector that has to be broadcasted in column-major with zero stride (e.g., as {B1.device_data(), 0}) for the DualGemm implementation to be able to process B0 and B1 simultaneously.

In this commit, LayoutB0 and LayoutB1 are decoupled throughout the DualGemm code (device, kernel, and mma). Additionally, the batch strides of B0 and B1 are also decoupled to accommodate the column vector B1 case described above.

* Remove comment as no longer relevant

* Revert Fix MHA kernel

---------

Co-authored-by: mikeiovine <mikeiovine@fb.com>
This commit is contained in:
Adnan Akhundov
2023-02-13 15:27:13 -05:00
committed by GitHub
co-authored by mikeiovine
parent ce8597dc14
commit 3c995c7606
7 changed files with 793 additions and 305 deletions
+98 -42
View File
@@ -42,6 +42,7 @@
#include "../threadblock/dual_mma_multistage.h"
#include "../threadblock/dual_epilogue.h"
#include "../dual_gemm_common.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
@@ -92,6 +93,10 @@ struct DualGemm {
true // IterationsUnroll
>;
using ElementA = typename DualMma::IteratorA::Element;
using ElementB = typename DualMma::IteratorB0::Element;
using ElementC = typename DualEpilogue::OutputTileIterator::Element;
static bool const kSplitKSerial = SplitKSerial;
static_assert(!kSplitKSerial || (kStoreD0 && kStoreD1),
"Split-K serial requires buffers for D0/D1 for reduction");
@@ -102,6 +107,7 @@ struct DualGemm {
/// Parameters structure
struct Params {
DualGemmMode mode;
cutlass::gemm::GemmCoord problem_size;
cutlass::gemm::GemmCoord grid_tiled_shape;
int swizzle_log_tile;
@@ -109,8 +115,8 @@ struct DualGemm {
// Mma0
typename DualMma::IteratorA::Params params_A0;
typename DualMma::IteratorA::TensorRef ref_A0;
typename DualMma::IteratorB::Params params_B0;
typename DualMma::IteratorB::TensorRef ref_B0;
typename DualMma::IteratorB0::Params params_B0;
typename DualMma::IteratorB0::TensorRef ref_B0;
typename Epilogue0::OutputTileIterator::Params params_C0;
typename Epilogue0::OutputTileIterator::TensorRef ref_C0;
typename Epilogue0::OutputTileIterator::Params params_D0;
@@ -118,8 +124,8 @@ struct DualGemm {
typename OutputOp0::Params output_op_0;
// Mma1
typename DualMma::IteratorB::Params params_B1;
typename DualMma::IteratorB::TensorRef ref_B1;
typename DualMma::IteratorB1::Params params_B1;
typename DualMma::IteratorB1::TensorRef ref_B1;
typename Epilogue1::OutputTileIterator::Params params_C1;
typename Epilogue1::OutputTileIterator::TensorRef ref_C1;
typename Epilogue1::OutputTileIterator::Params params_D1;
@@ -133,6 +139,12 @@ struct DualGemm {
int *semaphore;
int gemm_k_size;
int64_t batch_stride_A;
int64_t batch_stride_B0;
int64_t batch_stride_B1;
int64_t batch_stride_C;
int64_t batch_stride_D;
//
// Methods
//
@@ -142,15 +154,16 @@ struct DualGemm {
CUTLASS_HOST_DEVICE
Params(
DualGemmMode mode,
cutlass::gemm::GemmCoord const & problem_size,
cutlass::gemm::GemmCoord const & grid_tiled_shape,
// Mma0: D0 = A @ B0 + C0
typename DualMma::IteratorA::TensorRef ref_A0,
typename DualMma::IteratorB::TensorRef ref_B0,
typename DualMma::IteratorB0::TensorRef ref_B0,
typename Epilogue0::OutputTileIterator::TensorRef ref_C0,
typename Epilogue0::OutputTileIterator::TensorRef ref_D0,
// Mma1: D1 = A @ B1 + C1
typename DualMma::IteratorB::TensorRef ref_B1,
typename DualMma::IteratorB1::TensorRef ref_B1,
typename Epilogue1::OutputTileIterator::TensorRef ref_C1,
typename Epilogue1::OutputTileIterator::TensorRef ref_D1,
@@ -158,8 +171,14 @@ struct DualGemm {
typename OutputOp0::Params output_op_0 = typename OutputOp0::Params(),
typename OutputOp1::Params output_op_1 = typename OutputOp1::Params(),
typename OutputOp2::Params output_op_2 = typename OutputOp2::Params(),
int *workspace = nullptr
int *workspace = nullptr,
int64_t batch_stride_A = 1,
int64_t batch_stride_B0 = 1,
int64_t batch_stride_B1 = 1,
int64_t batch_stride_C = 1,
int64_t batch_stride_D = 1
):
mode(mode),
problem_size(problem_size),
grid_tiled_shape(grid_tiled_shape),
swizzle_log_tile(ThreadblockSwizzle().get_log_tile(grid_tiled_shape)),
@@ -183,13 +202,18 @@ struct DualGemm {
ref_D2(ref_D2),
output_op_0(output_op_0),
output_op_1(output_op_1),
output_op_2(output_op_2) {
output_op_2(output_op_2),
batch_stride_A(batch_stride_A),
batch_stride_B0(batch_stride_B0),
batch_stride_B1(batch_stride_B1),
batch_stride_C(batch_stride_C),
batch_stride_D(batch_stride_D) {
int total_gemm_k_iterations = (problem_size.k() + DualMma::Shape::kK - 1) / DualMma::Shape::kK;
int gemm_k_iterations = (total_gemm_k_iterations + grid_tiled_shape.k() - 1) / grid_tiled_shape.k();
gemm_k_size = gemm_k_iterations * DualMma::Shape::kK;
semaphore = workspace;
semaphore = workspace;
}
};
@@ -210,16 +234,16 @@ struct DualGemm {
static Status can_implement(
cutlass::gemm::GemmCoord const & problem_size,
typename DualMma::IteratorA::TensorRef ref_A0,
typename DualMma::IteratorB::TensorRef ref_B0,
typename DualMma::IteratorB0::TensorRef ref_B0,
typename Epilogue0::OutputTileIterator::TensorRef ref_C0,
typename Epilogue0::OutputTileIterator::TensorRef ref_D0,
typename DualMma::IteratorB::TensorRef ref_B1,
typename DualMma::IteratorB1::TensorRef ref_B1,
typename Epilogue1::OutputTileIterator::TensorRef ref_C1,
typename Epilogue1::OutputTileIterator::TensorRef ref_D1,
typename Epilogue1::OutputTileIterator::TensorRef ref_D2) {
static int const kAlignmentA = DualMma::IteratorA::AccessType::kElements;
static int const kAlignmentB = DualMma::IteratorB::AccessType::kElements;
static int const kAlignmentB = DualMma::IteratorB0::AccessType::kElements;
static int const kAlignmentC = Epilogue0::OutputTileIterator::kElementsPerAccess;
if (!TensorRef_aligned(ref_A0, kAlignmentA)) {
@@ -273,52 +297,66 @@ struct DualGemm {
return;
}
int offset_k = 0;
int problem_size_k = params.problem_size.k();
ElementA *ptr_A0 = static_cast<ElementA *>(params.ref_A0.data());
ElementB *ptr_B0 = static_cast<ElementB *>(params.ref_B0.data());
ElementB *ptr_B1 = static_cast<ElementB *>(params.ref_B1.data());
//
// Fetch pointers based on mode.
//
if (params.mode == DualGemmMode::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 == DualGemmMode::kBatched) {
ptr_A0 += threadblock_tile_offset.k() * params.batch_stride_A;
ptr_B0 += threadblock_tile_offset.k() * params.batch_stride_B0;
ptr_B1 += threadblock_tile_offset.k() * params.batch_stride_B1;
}
// Compute initial location in logical coordinates
cutlass::MatrixCoord tb_offset_A0{
threadblock_tile_offset.m() * DualMma::Shape::kM,
threadblock_tile_offset.k() * params.gemm_k_size,
offset_k,
};
cutlass::MatrixCoord tb_offset_B0{
threadblock_tile_offset.k() * params.gemm_k_size,
offset_k,
threadblock_tile_offset.n() * DualMma::Shape::kN
};
cutlass::MatrixCoord tb_offset_B1{
threadblock_tile_offset.k() * params.gemm_k_size,
offset_k,
threadblock_tile_offset.n() * DualMma::Shape::kN
};
// Problem size is a function of threadblock index in the K dimension
int problem_size_k =
(params.problem_size.k() < (threadblock_tile_offset.k() + 1) * params.gemm_k_size) ?
params.problem_size.k() :
(threadblock_tile_offset.k() + 1) * params.gemm_k_size;
// Compute threadblock-scoped matrix multiply-add
int gemm_k_iterations = (problem_size_k - tb_offset_A0.column() + DualMma::Shape::kK - 1) / DualMma::Shape::kK;
// Compute position within threadblock
int thread_idx = threadIdx.x;
// Construct iterators to A and B operands
typename DualMma::IteratorA iterator_A0(
params.params_A0,
params.ref_A0.data(),
ptr_A0,
{params.problem_size.m(), problem_size_k},
thread_idx,
tb_offset_A0);
typename DualMma::IteratorB iterator_B0(
typename DualMma::IteratorB0 iterator_B0(
params.params_B0,
params.ref_B0.data(),
ptr_B0,
{problem_size_k, params.problem_size.n()},
thread_idx,
tb_offset_B0);
typename DualMma::IteratorB iterator_B1(
typename DualMma::IteratorB1 iterator_B1(
params.params_B1,
params.ref_B1.data(),
ptr_B1,
{problem_size_k, params.problem_size.n()},
thread_idx,
tb_offset_B1);
@@ -340,6 +378,9 @@ struct DualGemm {
accum0.clear();
accum1.clear();
// Compute threadblock-scoped matrix multiply-add
int gemm_k_iterations = (problem_size_k - offset_k + DualMma::Shape::kK - 1) / DualMma::Shape::kK;
DualMma mma(shared_storage.main_loop, thread_idx, warp_idx, lane_idx);
if (!kSplitKSerial || gemm_k_iterations > 0) {
// Compute threadblock-scoped matrix multiply-add
@@ -372,31 +413,46 @@ struct DualGemm {
int block_idx = threadblock_tile_offset.m() + threadblock_tile_offset.n() * params.grid_tiled_shape.m();
ElementC *ptr_C0 = static_cast<ElementC *>(params.ref_C0.data());
ElementC *ptr_C1 = static_cast<ElementC *>(params.ref_C1.data());
ElementC *ptr_D0 = static_cast<ElementC *>(params.ref_D0.data());
ElementC *ptr_D1 = static_cast<ElementC *>(params.ref_D1.data());
ElementC *ptr_D2 = static_cast<ElementC *>(params.ref_D2.data());
// Construct the semaphore.
Semaphore semaphore(params.semaphore + block_idx, thread_idx);
// If performing a reduction via split-K, fetch the initial synchronization
if (kSplitKSerial && params.grid_tiled_shape.k() > 1) {
// Fetch the synchronization lock initially but do not block.
semaphore.fetch();
if (params.mode == DualGemmMode::kGemm) {
// If performing a reduction via split-K, fetch the initial synchronization
if (kSplitKSerial && params.grid_tiled_shape.k() > 1) {
// Fetch the synchronization lock initially but do not block.
semaphore.fetch();
// Indicate which position in a serial reduction the output operator is currently updating
output_op_0.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
output_op_1.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
// Indicate which position in a serial reduction the output operator is currently updating
output_op_0.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
output_op_1.set_k_partition(threadblock_tile_offset.k(), params.grid_tiled_shape.k());
}
}
else if (params.mode == DualGemmMode::kBatched) {
ptr_C0 += threadblock_tile_offset.k() * params.batch_stride_C;
ptr_C1 += threadblock_tile_offset.k() * params.batch_stride_C;
ptr_D0 += threadblock_tile_offset.k() * params.batch_stride_D;
ptr_D1 += threadblock_tile_offset.k() * params.batch_stride_D;
ptr_D2 += threadblock_tile_offset.k() * params.batch_stride_D;
}
// Tile iterator loading from source tensor.
typename Epilogue0::OutputTileIterator iterator_C0(
params.params_C0,
params.ref_C0.data(),
ptr_C0,
params.problem_size.mn(),
thread_idx,
threadblock_offset
);
typename Epilogue1::OutputTileIterator iterator_C1(
params.params_C1,
params.ref_C1.data(),
ptr_C1,
params.problem_size.mn(),
thread_idx,
threadblock_offset
@@ -405,21 +461,21 @@ struct DualGemm {
// Tile iterator writing to destination tensor.
typename Epilogue0::OutputTileIterator iterator_D0(
params.params_D0,
params.ref_D0.data(),
ptr_D0,
params.problem_size.mn(),
thread_idx,
threadblock_offset
);
typename Epilogue1::OutputTileIterator iterator_D1(
params.params_D1,
params.ref_D1.data(),
ptr_D1,
params.problem_size.mn(),
thread_idx,
threadblock_offset
);
typename Epilogue1::OutputTileIterator iterator_D2(
params.params_D2,
params.ref_D2.data(),
ptr_D2,
params.problem_size.mn(),
thread_idx,
threadblock_offset