[QST] Epilogue Broadcast: `Adapter` vs `GemmUniversal`
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 10.5k
- Forks
- 2.1k
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 7
Description
What is your question?
Trying to understand the behavior of Gemm with a column-broadcasted bias vector epilogue.
When defining a device GemmUniversalWithBroadcast with the following config:
using DType = cutlass::half_t;
using ElementWiseOp = cutlass::epilogue::thread::Identity<DType>;
using BinaryOp = cutlass::plus<DType>;
using LayoutA = cutlass::layout::RowMajor;
using LayoutB = cutlass::layout::RowMajor;
using LayoutC = cutlass::layout::RowMajor;
constexpr int stages = 3;
using ThreadBlockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<1>;
using EpilogueOutputOp = cutlass::epilogue::thread::LinearCombinationBiasElementwise<
DType,
DType,
DType,
DType,
DType,
8,
ElementWiseOp,
BinaryOp>;
using GemmUniversal = cutlass::gemm::device::GemmUniversalWithBroadcast<
DType,
LayoutA,
DType, LayoutB,
DType, LayoutC,
DType,
cutlass::arch::OpClassTensorOp,
cutlass::arch::Sm80,
cutlass::gemm::GemmShape<128, 128, 32>,
cutlass::gemm::GemmShape<64, 64, 32>,
cutlass::gemm::GemmShape<16, 8, 16>,
EpilogueOutputOp,
ThreadBlockSwizzle,
stages>;
I get a core dump whenever I try to run the above with M != K. Running with M == N, I get the correct GEMM but the epilogue is broadcasted incorrectly (row-wise vs column-wise).
When I run the above using GemmUniversalAdapter as the device handle, the op runs for all M and N. However, the A and B inputs transposed because of an internal transpose that the adapter does, while the epilogue op is performed correctly.
Questions
- How to properly instantiate / use a
GemmUniversalWithBroadcast? - Why does the
GemmUniversalAdaptertranspose layouts internally?
Repro
Here is a simple script for reproducing above.
GemmUniversalWithBroadcastwill fail to run withM != NGemmUniversalWithBroadcastruns withM == Nbut epilogue incorrectGemmUniversalAdapterruns, but with operandsAandBtransposed.
#include <iostream>
#include "cutlass/cutlass.h"
#include "cutlass/functional.h"
#include "cutlass/gemm/kernel/default_gemm_with_broadcast.h"
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/epilogue/thread/linear_combination_bias_elementwise.h"
#include "cutlass/epilogue/thread/linear_combination_bias_relu.h"
#include "cutlass/util/host_tensor.h"
#include "cutlass/util/tensor_view_io.h"
#include "cutlass/util/reference/host/tensor_fill.h"
#include "cutlass/util/reference/host/tensor_copy.h"
#include "cutlass/util/reference/host/tensor_compare.h"
#include "cutlass/util/reference/host/gemm.h"
#include "cutlass/gemm/device/gemm_universal_with_broadcast.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
#define CUTLASS_CHECK(status) \
{ \
cutlass::Status error = status; \
if (error != cutlass::Status::kSuccess) \
{ \
std::cerr << "Got cutlass error: " << cutlassGetStatusString(error) << " at: " << __LINE__ \
<< std::endl; \
exit(EXIT_FAILURE); \
} \
}
using DType = cutlass::half_t;
using ElementWiseOp = cutlass::epilogue::thread::Identity<DType>;
using BinaryOp = cutlass::plus<DType>;
using LayoutA = cutlass::layout::RowMajor;
using LayoutB = cutlass::layout::RowMajor;
using LayoutC = cutlass::layout::RowMajor;
constexpr int stages = 3;
using ThreadBlockSwizzle = cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<1>;
using EpilogueOutputOp = cutlass::epilogue::thread::LinearCombinationBiasElementwise<
DType,
DType,
DType,
DType,
DType,
8,
ElementWiseOp,
BinaryOp>;
using GemmKernel =
typename cutlass::gemm::kernel::DefaultGemmWithBroadcast<
DType, LayoutA, cutlass::ComplexTransform::kNone, 8, // transposed B operand
DType, LayoutB, cutlass::ComplexTransform::kNone, 8, // transposed A operand
DType, LayoutC,
DType,
cutlass::arch::OpClassTensorOp,
cutlass::arch::Sm80,
cutlass::gemm::GemmShape<128, 128, 32>,
cutlass::gemm::GemmShape<64, 64, 32>,
cutlass::gemm::GemmShape<16, 8, 16>,
EpilogueOutputOp,
ThreadBlockSwizzle,
stages,
cutlass::arch::OpMultiplyAdd>::GemmKernel;
using GemmUniversal = cutlass::gemm::device::GemmUniversalWithBroadcast<
DType,
LayoutA,
DType, LayoutB,
DType, LayoutC,
DType,
cutlass::arch::OpClassTensorOp,
cutlass::arch::Sm80,
cutlass::gemm::GemmShape<128, 128, 32>,
cutlass::gemm::GemmShape<64, 64, 32>,
cutlass::gemm::GemmShape<16, 8, 16>,
EpilogueOutputOp,
ThreadBlockSwizzle,
stages>;
using GemmAdapter = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
template <typename Gemm>
void test(int M = 8, int N = 4, int K = 8, bool verbose = true, int batch_count = 1,
cutlass::gemm::GemmUniversalMode mode = cutlass::gemm::GemmUniversalMode::kGemm,
DType alpha = DType(1.0), DType beta = DType(0.0))
{
cutlass::gemm::GemmCoord problem_size = cutlass::gemm::GemmCoord(M, N, K);
cutlass::HostTensor<typename Gemm::ElementA, typename Gemm::LayoutA> tensor_A;
cutlass::HostTensor<typename Gemm::ElementB, typename Gemm::LayoutB> tensor_B;
cutlass::HostTensor<EpilogueOutputOp::ElementZ, typename Gemm::LayoutC> tensor_Z;
cutlass::HostTensor<EpilogueOutputOp::ElementVector, typename Gemm::LayoutC> tensor_Broadcast;
tensor_A.resize({problem_size.m(), problem_size.k()});
tensor_B.resize({problem_size.k(), problem_size.n()});
tensor_Z.resize({problem_size.m(), problem_size.n()});
tensor_Broadcast.resize({problem_size.m(), 1});
cutlass::reference::host::BlockFillSequential(tensor_A.host_data(), tensor_A.capacity());
cutlass::reference::host::BlockFill(tensor_B.host_data(), tensor_B.capacity(), typename Gemm::ElementB(1.0));
// cutlass::reference::host::BlockFillSequential(tensor_B.host_data(), tensor_B.capacity());
cutlass::reference::host::BlockFill(tensor_Z.host_data(), tensor_Z.capacity(), EpilogueOutputOp::ElementZ(0.0));
cutlass::reference::host::BlockFillSequential(tensor_Broadcast.host_data(), tensor_Broadcast.capacity());
tensor_A.sync_device();
tensor_B.sync_device();
tensor_Z.sync_device();
tensor_Broadcast.sync_device();
if (verbose)
{
std::cout << "tensor_A:\n"
<< tensor_A.host_view() << std::endl;
std::cout << "tensor_B:\n"
<< tensor_B.host_view() << std::endl;
std::cout << "tensor_Broadcast:\n"
<< tensor_Broadcast.host_view() << std::endl;
}
typename Gemm::Arguments arguments{
mode,
problem_size,
batch_count,
{alpha, beta},
tensor_A.device_data(),
tensor_B.device_data(),
nullptr, // C
tensor_Z.device_data(),
tensor_Broadcast.device_data(),
nullptr, // T
problem_size.m() * problem_size.k(), // batch stride A
problem_size.n() * problem_size.k(), // batch stride B
problem_size.m() * problem_size.n(), // batch stride C
problem_size.m() * problem_size.n(), // batch stride Z
problem_size.m(), // batch stride broadcast
problem_size.m() * problem_size.n(), // batch stride T
tensor_A.layout().stride(0), // stride A
tensor_B.layout().stride(0), // stride B
tensor_Z.layout().stride(0), // stride C
tensor_Z.layout().stride(0), // stride Z
0, // This must be zero for broadcast
tensor_Z.layout().stride(0), // stride T
};
Gemm gemm_op;
size_t workspace_size = Gemm::get_workspace_size(arguments);
cutlass::device_memory::allocation<uint8_t> workspace(workspace_size);
cutlass::Status status = gemm_op.initialize(arguments, workspace.get());
CUTLASS_CHECK(status);
status = gemm_op();
CUTLASS_CHECK(status);
tensor_Z.sync_host();
std::cout << "tensor_Z:\n"
<< tensor_Z.host_view() << std::endl;
}
int main()
{
int M = 8;
int N = 8;
int K = 8;
// NOTE: Running with `GemmUniversalBroadcast` will segfault if M != N
std::cout << "GemmUniversalBroadcast" << std::endl;
test<GemmUniversal>(M, N, K);
std::cout << " ----------------------- " << std::endl;
std::cout << "GemmAdapterBroadcast" << std::endl;
test<GemmAdapter>(M, N, K);
}
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with include/cutlass/gemm/device/gemm_universal_with_broadcast.h, include/cutlass/gemm/device/gemm_universal_adapter.h, and the linked adapter lines, then compile and run the supplied reproducer with unequal M and N. Compare both device handles and their argument layouts; done means the broadcast epilogue works with the direct handle and the adapter's operand behavior is explained or corrected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100