Files
TheRock/rocm-libraries/shared/rocroller/test/unit/MatrixMultiplyTestBase.hpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

858 lines
40 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/CommandSolution.hpp>
#include <rocRoller/Operations/Command.hpp>
#include <rocRoller/Parameters/Solution/LoadOption.hpp>
#include <rocRoller/TensorDescriptor.hpp>
#include <common/mxDataGen.hpp>
#include "GPUContextFixture.hpp"
using namespace rocRoller;
namespace MatrixMultiplyTest
{
template <typename T>
concept isF8 = std::is_same_v<T, FP8> || std::is_same_v<T, BF8>;
template <typename T>
concept isF6F4 = std::is_same_v<T, FP6> || std::is_same_v<T, BF6> || std::is_same_v<T, FP4>;
template <typename T>
concept isF16 = std::is_same_v<T, Half> || std::is_same_v<T, BFloat16>;
template <typename T>
concept isF32 = std::is_same_v<T, float>;
/**
* @brief Return a reasonable random value range for datatype T.
*
* The return value is usually passed to the random generator to
* obtain values in (-range, range), and these will be used to
* populate matrices for (small) GEMM problems.
*
* The value returned *may or may not* correspond to the maximum
* representable value of T.
*/
template <typename T>
float range()
{
// Not the maximum range.
if constexpr(std::is_same_v<T, float> || std::is_same_v<T, Half>)
return 10.f;
// Maximum range
if constexpr(std::is_same_v<T, FP8>)
return 448.f;
// Maximum range; kinda extreme
if constexpr(std::is_same_v<T, BF8>)
return 57344.f;
// Maximum range
if constexpr(std::is_same_v<T, FP6>)
return 7.5f;
// Maximum range
if constexpr(std::is_same_v<T, BF6>)
return 28.f;
// FP4, maximum range
return 6.f;
}
struct ScaleParams
{
DataType scaleTypeA = DataType::None;
DataType scaleTypeB = DataType::None;
uint scaleBlockSize = 1;
};
template <typename... Ts>
class BaseMatrixMultiplyContextFixture
: public BaseGPUContextFixture,
public ::testing::WithParamInterface<std::tuple<rocRoller::GPUArchitectureTarget, Ts...>>
{
protected:
virtual rocRoller::ContextPtr createContext() override
{
GPUArchitectureTarget device = std::get<0>(this->GetParam());
return this->createContextForArch(device);
}
public:
CommandKernelPtr commandKernel;
template <typename TA,
typename TB,
typename TD,
typename ACC = float,
DataType STA = DataType::None,
DataType STB = DataType::None>
void matrixMultiplyMacroTile(int wave_m,
int wave_n,
int wave_k,
int wave_b,
Parameters::Solution::LoadPath loadPathB
= Parameters::Solution::LoadPath::BufferToLDSViaVGPR,
std::string transA = "N",
std::string transB = "N",
const ScaleParams scaleParams = {})
{
commandKernel = nullptr;
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA, GPUCapability::HasWMMA);
if constexpr(isF8<TA> || isF8<TB>)
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA_fp8,
GPUCapability::HasWMMA_f32_16x16x16_f8,
GPUCapability::HasWMMA_f32_16x16x64_f8,
GPUCapability::HasWMMA_f16_16x16x64_f8,
GPUCapability::HasWMMA_f32_16x16x128_f8,
GPUCapability::HasWMMA_f16_16x16x128_f8);
}
if constexpr(isF6F4<TA> || isF6F4<TB>)
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA_f8f6f4,
GPUCapability::HasWMMA_f8f6f4,
GPUCapability::HasWMMA_32x16x128_f4);
}
if constexpr(isF32<TA> || isF32<TB>)
{
REQUIRE_ARCH_CAP(GPUCapability::HasWMMA_f32_16x16x4_f32);
}
if((isF8<TA> || isF8<TB>)&&(wave_k >= 64))
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA_f8f6f4,
GPUCapability::HasWMMA_f8f6f4);
}
const bool scaleA = scaleParams.scaleTypeA != DataType::None;
const bool scaleB = scaleParams.scaleTypeB != DataType::None;
const auto scaleTypeA = scaleParams.scaleTypeA;
const auto scaleTypeB = scaleParams.scaleTypeB;
const auto scaleBlockSize = scaleParams.scaleBlockSize;
if(scaleA || scaleB)
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA_scale_f8f6f4,
GPUCapability::HasWMMA_scale_f8f6f4,
GPUCapability::HasWMMA_scale16_f8f6f4,
GPUCapability::HasWMMA_scale_32x16x128_f4,
GPUCapability::HasWMMA_scale16_32x16x128_f4);
const auto& arch = m_context->targetArchitecture();
AssertFatal(!scaleA || arch.isSupportedScaleType(scaleTypeA),
fmt::format("Scale A set but target {} does not support scale type {}.",
arch.target().toString(),
toString(scaleTypeA)));
AssertFatal(!scaleB || arch.isSupportedScaleType(scaleTypeB),
fmt::format("Scale B set but target {} does not support scale type {}.",
arch.target().toString(),
toString(scaleTypeB)));
}
auto dataTypeA = TypeInfo<TA>::Var.dataType;
auto dataTypeB = TypeInfo<TB>::Var.dataType;
auto dataTypeD = TypeInfo<TD>::Var.dataType;
auto dataTypeAcc = TypeInfo<ACC>::Var.dataType;
// matrix size: A is MxK; B is KxN; D is MxN
int mac_m = wave_m;
int mac_n = wave_n;
int mac_k = 32;
unsigned M = mac_m;
unsigned N = mac_n;
unsigned K = 32;
if constexpr(isF8<TA> && isF8<TB>)
{
mac_k = 2 * wave_k;
K = 2 * mac_k;
}
if constexpr(isF6F4<TA> || isF6F4<TB>)
{
mac_k = 2 * wave_k;
K = 4 * mac_k;
}
if constexpr(isF16<TA> || isF16<TB>)
{
mac_k = 4 * wave_k;
K = 8 * mac_k;
}
if constexpr(isF32<TA> || isF32<TB>)
{
mac_k = 4 * wave_k;
K = 8 * mac_k;
}
Log::debug("MatrixMultiplyMacroTile: Matrix {}x{}x{}", M, N, K);
Log::debug("MatrixMultiplyMacroTile: WGTile {}x{}x{}", mac_m, mac_n, mac_k);
Log::debug("MatrixMultiplyMacroTile: MI {}x{}x{}x{}", wave_m, wave_n, wave_k, wave_b);
AssertFatal(M % mac_m == 0, "MacroTile size mismatch (M)");
AssertFatal(N % mac_n == 0, "MacroTile size mismatch (N)");
AssertFatal(K % mac_k == 0, "MacroTile size mismatch (K)");
AssertFatal(mac_m == wave_m, "Single output MacroTile.");
AssertFatal(mac_n == wave_n, "Single output MacroTile.");
auto const& arch = m_context->targetArchitecture();
uint workgroup_size_x = arch.GetCapability(GPUCapability::DefaultWavefrontSize);
uint workgroup_size_y = 1;
auto bpe = CeilDivide(DataTypeInfo::Get(dataTypeA).elementBits, 8u);
AssertFatal(mac_m * mac_k * bpe > wave_m * wave_k, "Not enough elements.");
auto NX = std::make_shared<Expression::Expression>(workgroup_size_x);
auto NY = std::make_shared<Expression::Expression>(workgroup_size_y);
auto NZ = std::make_shared<Expression::Expression>(1u);
std::vector<size_t> unitStridesN = {1, 0};
std::vector<size_t> unitStridesT = {0, 1};
auto command = std::make_shared<Command>();
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(
2, dataTypeA, {}, transA == "N" ? unitStridesN : unitStridesT));
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB = command->addOperation(rocRoller::Operations::Tensor(
2, dataTypeB, {}, transB == "N" ? unitStridesN : unitStridesT));
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
std::optional<rocRoller::Operations::OperationTag> tagTensorScaleA, tagLoadScaleA,
tagTensorScaleB, tagLoadScaleB;
if(scaleA)
{
tagTensorScaleA = command->addOperation(rocRoller::Operations::Tensor(
2, scaleTypeA, {}, transA == "N" ? unitStridesN : unitStridesT));
tagLoadScaleA
= command->addOperation(rocRoller::Operations::T_Load_Tiled(*tagTensorScaleA));
}
if(scaleB)
{
tagTensorScaleB = command->addOperation(rocRoller::Operations::Tensor(
2, scaleTypeB, {}, transB == "N" ? unitStridesN : unitStridesT));
tagLoadScaleB
= command->addOperation(rocRoller::Operations::T_Load_Tiled(*tagTensorScaleB));
}
rocRoller::Operations::OperationTag tagStoreD;
if(!scaleA)
{
ASSERT_FALSE(scaleB);
tagStoreD = command->addOperation(
rocRoller::Operations::T_Mul(tagLoadA, tagLoadB, dataTypeAcc)); // D = A * B
}
else
{
ASSERT_TRUE(scaleB);
AssertFatal(
arch.isSupportedScaleBlockSize(scaleBlockSize),
fmt::format("Architecture {} does not support block scaling (size: {}).",
arch.target().toString(),
scaleBlockSize));
auto scaledA = command->addOperation(rocRoller::Operations::BlockScale(
tagLoadA, 2, tagLoadScaleA, {1, scaleBlockSize}));
auto scaledB = command->addOperation(rocRoller::Operations::BlockScale(
tagLoadB, 2, tagLoadScaleB, {scaleBlockSize, 1}));
tagStoreD = command->addOperation(
rocRoller::Operations::T_Mul(scaledA, scaledB, dataTypeAcc)); // D = A * B
}
auto tagTensorD = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeD));
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagStoreD, tagTensorD));
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
params->packMultipleElementsInto1VGPR = true;
params->enableLongDwordInstructions = true;
params->transposeMemoryAccess.set(LayoutType::MATRIX_A, transA == "T");
params->transposeMemoryAccess.set(LayoutType::MATRIX_B, transB == "T");
// TODO: the translate step should figure out that there is a
// T_Mul and do the right thing for the T_Load_Tiled commands
auto macTileA
= KernelGraph::CoordinateGraph::MacroTile({mac_m, mac_k},
LayoutType::MATRIX_A,
{wave_m, wave_n, wave_k, wave_b},
MemoryType::WAVE_LDS);
params->setDimensionInfo(tagLoadA, macTileA);
if(scaleA)
{
AssertFatal(wave_k % scaleBlockSize == 0,
fmt::format("wave_k: {} must be a multiple of the scale block size: {}",
wave_k,
scaleBlockSize));
auto macTileScaleA = KernelGraph::CoordinateGraph::MacroTile(
{mac_m, static_cast<int>(mac_k / scaleBlockSize)},
LayoutType::MATRIX_A,
{wave_m, wave_n, static_cast<int>(wave_k / scaleBlockSize), wave_b},
MemoryType::WAVE);
params->setDimensionInfo(tagLoadScaleA.value(), macTileScaleA);
}
auto macTileB
= KernelGraph::CoordinateGraph::MacroTile({mac_k, mac_n},
LayoutType::MATRIX_B,
{wave_m, wave_n, wave_k, wave_b},
GetMemoryType(loadPathB));
params->setDimensionInfo(tagLoadB, macTileB);
if(scaleB)
{
AssertFatal(wave_k % scaleBlockSize == 0,
fmt::format("wave_k: {} must be a multiple of the scale block size: {}",
wave_k,
scaleBlockSize));
auto macTileScaleB = KernelGraph::CoordinateGraph::MacroTile(
{static_cast<int>(mac_k / scaleBlockSize), mac_n},
LayoutType::MATRIX_B,
{wave_m, wave_n, static_cast<int>(wave_k / scaleBlockSize), wave_b},
MemoryType::WAVE);
params->setDimensionInfo(tagLoadScaleB.value(), macTileScaleB);
}
params->setManualWavefrontCount({1u, 1u});
commandKernel = std::make_shared<CommandKernel>(command, "MatrixMultiplyMacroTile");
commandKernel->setContext(m_context);
commandKernel->setCommandParameters(params);
commandKernel->generateKernel();
if(isLocalDevice())
{
TensorDescriptor descA(dataTypeA, {M, K}, transA);
TensorDescriptor descB(dataTypeB, {K, N}, transB);
TensorDescriptor descD(dataTypeD, {M, N}, {1u, M});
float rangeA = range<TA>();
float rangeB = range<TB>();
uint32_t seed = 9861u;
auto blockScalingA = (scaleA) ? scaleBlockSize : 1;
auto blockScalingB = (scaleB) ? scaleBlockSize : 1;
const auto dgenA
= getDataGenerator<TA, STA>(descA, -rangeA, rangeA, seed, blockScalingA);
const auto dgenB
= getDataGenerator<TB, STB>(descB, -rangeB, rangeB, seed, blockScalingB);
auto A = getRandomVector<TA, STA>(dgenA, scaleA);
auto B = getRandomVector<TB, STB>(dgenB, scaleB);
std::vector<uint8_t> hostScaleA, hostScaleB;
auto d_A = make_shared_device(A);
auto d_B = make_shared_device(B);
auto d_D = make_shared_device<ACC>(descD.totalAllocatedElements());
std::shared_ptr<uint8_t> d_scaleA, d_scaleB;
if(scaleA)
{
hostScaleA = dgenA.getScaleBytes();
d_scaleA = make_shared_device(hostScaleA);
}
if(scaleB)
{
hostScaleB = dgenB.getScaleBytes();
d_scaleB = make_shared_device(hostScaleB);
}
CommandArguments commandArgs = command->createArguments();
setCommandTensorArg(commandArgs, tagTensorA, descA, (TA*)d_A.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, (TB*)d_B.get());
setCommandTensorArg(commandArgs, tagTensorD, descD, d_D.get());
if(scaleA)
{
AssertFatal(K % scaleBlockSize == 0,
fmt::format("K: {} must be a multiple of the scale block size: {}",
K,
scaleBlockSize));
TensorDescriptor descScaleA(dataTypeA, {M, K / scaleBlockSize}, transA);
setCommandTensorArg(
commandArgs, tagTensorScaleA.value(), descScaleA, d_scaleA.get());
}
if(scaleB)
{
AssertFatal(K % scaleBlockSize == 0,
fmt::format("K: {} must be a multiple of the scale block size: {}",
K,
scaleBlockSize));
TensorDescriptor descScaleB(dataTypeB, {K / scaleBlockSize, N}, transB);
setCommandTensorArg(
commandArgs, tagTensorScaleB.value(), descScaleB, d_scaleB.get());
}
commandKernel->launchKernel(commandArgs.runtimeArguments());
std::vector<TD> D(descD.totalAllocatedElements());
ASSERT_THAT(hipMemcpy(D.data(),
d_D.get(),
descD.totalAllocatedElements() * sizeof(TD),
hipMemcpyDefault),
HasHipSuccess(0));
std::vector<TD> c_D(descD.totalAllocatedElements(), TD{});
std::vector<TD> c_C(descD.totalAllocatedElements(), TD{});
float alpha = 1.0f;
if(scaleA)
{
ASSERT_TRUE(scaleB);
rocRoller::ScaledCPUMM(c_D,
c_C,
A,
B,
hostScaleA,
hostScaleB,
M,
N,
K,
alpha,
0.0,
transA == "T",
transB == "T",
scaleBlockSize,
scaleTypeA,
scaleTypeB);
}
else
{
ASSERT_FALSE(scaleB);
CPUMM(c_D, c_C, A, B, M, N, K, alpha, 0.0, transA == "T", transB == "T");
}
auto tol = gemmAcceptableError<TA, TB, TD>(
M, N, K, m_context->targetArchitecture().target());
auto res = compare(D, c_D, tol);
Log::info("RNorm is {}", res.relativeNormL2);
ASSERT_TRUE(res.ok) << res.message();
}
}
template <typename TA, typename TB, DataType STA>
void matrixMultiplyMacroTileMixed(int m,
int n,
int k,
int b,
bool useLDSB = true,
std::string transA = "N",
std::string transB = "N",
const ScaleParams scaleParams = {})
{
if(isE8M0(scaleParams.scaleTypeB))
matrixMultiplyMacroTile<TA, TB, float, float, STA, DataType::E8M0>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(isE5M3(scaleParams.scaleTypeB))
matrixMultiplyMacroTile<TA, TB, float, float, STA, DataType::E5M3>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(isE4M3(scaleParams.scaleTypeB))
matrixMultiplyMacroTile<TA, TB, float, float, STA, DataType::E4M3>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(scaleParams.scaleTypeB == DataType::None)
matrixMultiplyMacroTile<TA, TB, float, float, STA, DataType::None>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else
Throw<FatalError>("Invalid type.");
}
template <typename TA, typename TB>
void matrixMultiplyMacroTileMixed(int m,
int n,
int k,
int b,
bool useLDSB = true,
std::string transA = "N",
std::string transB = "N",
const ScaleParams scaleParams = {})
{
if(isE8M0(scaleParams.scaleTypeA))
matrixMultiplyMacroTileMixed<TA, TB, DataType::E8M0>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(isE5M3(scaleParams.scaleTypeA))
matrixMultiplyMacroTileMixed<TA, TB, DataType::E5M3>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(isE4M3(scaleParams.scaleTypeA))
matrixMultiplyMacroTileMixed<TA, TB, DataType::E4M3>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else if(scaleParams.scaleTypeA == DataType::None)
matrixMultiplyMacroTileMixed<TA, TB, DataType::None>(
m, n, k, b, useLDSB, transA, transB, scaleParams);
else
Throw<FatalError>("Invalid type.");
}
template <typename TA>
void matrixMultiplyMacroTileMixed(rocRoller::DataType typeB,
int m,
int n,
int k,
int b,
Parameters::Solution::LoadPath loadPathB
= Parameters::Solution::LoadPath::BufferToLDSViaVGPR,
std::string transA = "N",
std::string transB = "N",
const ScaleParams scaleParams = {})
{
if(typeB == rocRoller::DataType::FP8)
matrixMultiplyMacroTile<TA, FP8, float>(
m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeB == rocRoller::DataType::BF8)
matrixMultiplyMacroTile<TA, BF8, float>(
m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeB == rocRoller::DataType::FP6)
matrixMultiplyMacroTile<TA, FP6, float>(
m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeB == rocRoller::DataType::BF6)
matrixMultiplyMacroTile<TA, BF6, float>(
m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeB == rocRoller::DataType::FP4)
matrixMultiplyMacroTile<TA, FP4, float>(
m, n, k, b, loadPathB, transA, transB, scaleParams);
else
Throw<FatalError>("Invalid type.");
}
void matrixMultiplyMacroTileMixed(rocRoller::DataType typeA,
rocRoller::DataType typeB,
int m,
int n,
int k,
int b,
Parameters::Solution::LoadPath loadPathB
= Parameters::Solution::LoadPath::BufferToLDSViaVGPR,
std::string transA = "N",
std::string transB = "N",
const ScaleParams scaleParams = {})
{
if(typeA == rocRoller::DataType::FP8)
matrixMultiplyMacroTileMixed<FP8>(
typeB, m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeA == rocRoller::DataType::BF8)
matrixMultiplyMacroTileMixed<BF8>(
typeB, m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeA == rocRoller::DataType::FP6)
matrixMultiplyMacroTileMixed<FP6>(
typeB, m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeA == rocRoller::DataType::BF6)
matrixMultiplyMacroTileMixed<BF6>(
typeB, m, n, k, b, loadPathB, transA, transB, scaleParams);
else if(typeA == rocRoller::DataType::FP4)
matrixMultiplyMacroTileMixed<FP4>(
typeB, m, n, k, b, loadPathB, transA, transB, scaleParams);
else
Throw<FatalError>("Invalid type.");
}
template <typename TA, typename TB, typename TD, typename ACC = float>
void matrixMultiplyAB(int wave_m,
int wave_n,
int wave_k,
int wave_b,
Parameters::Solution::LoadPath loadPathAB
= Parameters::Solution::LoadPath::BufferToLDSViaVGPR,
bool transA = false,
bool transB = false)
{
// matrix size: A is MxK; B is KxN; D is MxN
int const M = 1024;
int const N = 1024;
int const K = 512;
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA, GPUCapability::HasWMMA);
if constexpr(isF8<TA> || isF8<TB>)
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA_fp8,
GPUCapability::HasWMMA_f32_16x16x16_f8,
GPUCapability::HasWMMA_f32_16x16x64_f8,
GPUCapability::HasWMMA_f32_16x16x128_f8);
}
if constexpr(isF6F4<TA> || isF6F4<TB>)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_f8f6f4);
}
if constexpr(isF32<TA> || isF32<TB>)
{
REQUIRE_ARCH_CAP(GPUCapability::HasWMMA_f32_16x16x4_f32);
}
auto dataTypeA = TypeInfo<TA>::Var.dataType;
auto dataTypeB = TypeInfo<TB>::Var.dataType;
auto dataTypeD = TypeInfo<TD>::Var.dataType;
auto dataTypeAcc = TypeInfo<ACC>::Var.dataType;
const auto wavefrontCountX = 2;
const auto wavefrontCountY = 2;
// output macro tile size; we will launch 2x2 waves
int mac_m = wavefrontCountX * wave_m;
int mac_n = wavefrontCountY * wave_n;
int mac_k = 2 * wave_k;
AssertFatal(M % mac_m == 0, "MacroTile size mismatch (M)");
AssertFatal(N % mac_n == 0, "MacroTile size mismatch (N)");
auto arch = m_context->targetArchitecture();
const auto wfs = arch.GetCapability(GPUCapability::DefaultWavefrontSize);
uint workgroup_size_x = wavefrontCountX * wavefrontCountY * wfs;
uint workgroup_size_y = 1;
auto bpe = CeilDivide(DataTypeInfo::Get(dataTypeA).elementBits, 8u);
AssertFatal(mac_m * mac_k * bpe > wave_m * wave_k, "Not enough elements.");
uint num_workgroup_x = M / mac_m;
uint num_workgroup_y = N / mac_n;
auto NX = std::make_shared<Expression::Expression>(num_workgroup_x * workgroup_size_x);
auto NY = std::make_shared<Expression::Expression>(num_workgroup_y * workgroup_size_y);
auto NZ = std::make_shared<Expression::Expression>(1u);
TensorDescriptor descA(dataTypeA, {M, K}, transA ? "T" : "N");
TensorDescriptor descB(dataTypeB, {K, N}, transB ? "T" : "N");
TensorDescriptor descD(dataTypeD, {M, N}, {1u, M});
auto seed = 9861u;
auto A = DGenVector<TA>(descA, -1.f, 1.f, seed + 1);
auto B = DGenVector<TB>(descB, -1.f, 1.f, seed + 2);
auto d_A = make_shared_device(A);
auto d_B = make_shared_device(B);
auto d_D = make_shared_device<ACC>(M * N);
auto command = std::make_shared<Command>();
std::vector<size_t> unitStridesN = {1, 0};
std::vector<size_t> unitStridesT = {0, 1};
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(
2, dataTypeA, {}, transA ? unitStridesT : unitStridesN));
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB = command->addOperation(rocRoller::Operations::Tensor(
2, dataTypeB, {}, transB ? unitStridesT : unitStridesN)); // B
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
auto tagStoreD = command->addOperation(
rocRoller::Operations::T_Mul(tagLoadA, tagLoadB, dataTypeAcc)); // D = A * B
auto tagTensorD
= command->addOperation(rocRoller::Operations::Tensor(2, dataTypeD)); // D
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagStoreD, tagTensorD));
CommandArguments commandArgs = command->createArguments();
setCommandTensorArg(commandArgs, tagTensorA, descA, (TA*)d_A.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, (TB*)d_B.get());
setCommandTensorArg(commandArgs, tagTensorD, descD, d_D.get());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
// TODO: the translate step should figure out that there is a
// T_Mul and do the right thing for the T_Load_Tiled commands
auto macTileA
= KernelGraph::CoordinateGraph::MacroTile({mac_m, mac_k},
LayoutType::MATRIX_A,
{wave_m, wave_n, wave_k, wave_b},
GetMemoryType(loadPathAB));
auto macTileB
= KernelGraph::CoordinateGraph::MacroTile({mac_k, mac_n},
LayoutType::MATRIX_B,
{wave_m, wave_n, wave_k, wave_b},
GetMemoryType(loadPathAB));
params->setDimensionInfo(tagLoadA, macTileA);
params->setDimensionInfo(tagLoadB, macTileB);
params->setManualWavefrontCount({wavefrontCountX, wavefrontCountY});
params->transposeMemoryAccess.set(LayoutType::MATRIX_A, transA);
params->transposeMemoryAccess.set(LayoutType::MATRIX_B, transB);
CommandKernel commandKernel(command, "MatrixMultiplyAB");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
if(isLocalDevice())
{
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<TD> D(M * N);
ASSERT_THAT(hipMemcpy(D.data(), d_D.get(), M * N * sizeof(TD), hipMemcpyDefault),
HasHipSuccess(0));
std::vector<TD> c_D(M * N, TD{});
std::vector<TD> c_C(M * N, TD{});
CPUMM(c_D, c_C, A, B, M, N, K, 1.0f, 0.0, transA, transB);
auto tol = gemmAcceptableError<TA, TB, TD>(
M, N, K, m_context->targetArchitecture().target());
auto res = compare(D, c_D, tol);
Log::info("RNorm is {}", res.relativeNormL2);
ASSERT_TRUE(res.ok) << res.message();
}
}
template <typename T, typename ACC = float>
void matrixMultiplyABC(int wave_m,
int wave_n,
int wave_k,
int wave_b,
Parameters::Solution::LoadPath loadPathAB)
{
REQUIRE_ANY_OF_ARCH_CAP(GPUCapability::HasMFMA, GPUCapability::HasWMMA);
// matrix size: A is MxK; B is KxN; D is MxN
unsigned M = 1024;
unsigned N = 1024;
unsigned K = 512;
const auto wavefrontCountX = 2;
const auto wavefrontCountY = 2;
// output macro tile size
int mac_m = wavefrontCountX * wave_m;
int mac_n = wavefrontCountY * wave_n;
int mac_k = 2 * wave_k;
AssertFatal(M % mac_m == 0, "MacroTile size mismatch (M)");
AssertFatal(N % mac_n == 0, "MacroTile size mismatch (N)");
auto arch = m_context->targetArchitecture();
const auto wfs = arch.GetCapability(GPUCapability::DefaultWavefrontSize);
uint workgroup_size_x = wavefrontCountX * wavefrontCountY * wfs;
uint workgroup_size_y = 1;
uint num_workgroup_x = M / mac_m;
uint num_workgroup_y = N / mac_n;
auto NX = std::make_shared<Expression::Expression>(num_workgroup_x * workgroup_size_x);
auto NY = std::make_shared<Expression::Expression>(num_workgroup_y * workgroup_size_y);
auto NZ = std::make_shared<Expression::Expression>(1u);
auto dataType = TypeInfo<T>::Var.dataType;
auto dataTypeAcc = TypeInfo<ACC>::Var.dataType;
TensorDescriptor descA(dataType, {M, K}, {1u, M});
TensorDescriptor descB(dataType, {K, N}, {1u, K});
TensorDescriptor descC(dataType, {M, N}, {1u, M});
TensorDescriptor descD(dataType, {M, N}, {1u, M});
auto seed = 9861u;
auto A = DGenVector<T>(descA, -1.f, 1.f, seed + 1);
auto B = DGenVector<T>(descB, -1.f, 1.f, seed + 2);
auto C = DGenVector<T>(descC, -1.f, 1.f, seed + 3);
auto d_A = make_shared_device(A);
auto d_B = make_shared_device(B);
auto d_C = make_shared_device(C);
auto d_D = make_shared_device<T>(M * N);
auto command = std::make_shared<Command>();
auto tagTensorA
= command->addOperation(rocRoller::Operations::Tensor(2, dataType)); // A
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB
= command->addOperation(rocRoller::Operations::Tensor(2, dataType)); // B
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
auto tagTensorC
= command->addOperation(rocRoller::Operations::Tensor(2, dataType)); // C
auto tagLoadC = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorC));
auto tagAB = command->addOperation(
rocRoller::Operations::T_Mul(tagLoadA, tagLoadB, dataTypeAcc)); // A * B
auto execute = rocRoller::Operations::T_Execute(command->getNextTag());
auto tagStoreD
= execute.addXOp(rocRoller::Operations::E_Add(tagAB, tagLoadC)); // D = A * B + C
command->addOperation(std::move(execute));
auto tagTensorD
= command->addOperation(rocRoller::Operations::Tensor(2, dataType)); // D
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagStoreD, tagTensorD));
CommandArguments commandArgs = command->createArguments();
setCommandTensorArg(commandArgs, tagTensorA, descA, (T*)d_A.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, (T*)d_B.get());
setCommandTensorArg(commandArgs, tagTensorC, descC, (T*)d_C.get());
setCommandTensorArg(commandArgs, tagTensorD, descD, d_D.get());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
// TODO: the translate step should figure out that there is a
// T_Mul and do the right thing for the T_Load_Tiled commands
auto macTileA
= KernelGraph::CoordinateGraph::MacroTile({mac_m, mac_k},
LayoutType::MATRIX_A,
{wave_m, wave_n, wave_k, wave_b},
GetMemoryType(loadPathAB));
auto macTileB
= KernelGraph::CoordinateGraph::MacroTile({mac_k, mac_n},
LayoutType::MATRIX_B,
{wave_m, wave_n, wave_k, wave_b},
GetMemoryType(loadPathAB));
auto macTileC = KernelGraph::CoordinateGraph::MacroTile(
{mac_m, mac_n}, LayoutType::MATRIX_ACCUMULATOR, {wave_m, wave_n, wave_k, wave_b});
params->setDimensionInfo(tagLoadA, macTileA);
params->setDimensionInfo(tagLoadB, macTileB);
params->setDimensionInfo(tagLoadC, macTileC);
params->setManualWavefrontCount({wavefrontCountX, wavefrontCountY});
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
CommandKernel commandKernel(command, "ABC");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
if(isLocalDevice())
{
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<T> D(M * N);
ASSERT_THAT(hipMemcpy(D.data(), d_D.get(), M * N * sizeof(T), hipMemcpyDefault),
HasHipSuccess(0));
std::vector<T> c_D(M * N, T{});
CPUMM(c_D, C, A, B, M, N, K, 1.0, 1.0, false, false);
auto tol = gemmAcceptableError<T, T, T>(
M, N, K, m_context->targetArchitecture().target());
auto res = compare(D, c_D, tol);
Log::info("RNorm is {}", res.relativeNormL2);
ASSERT_TRUE(res.ok) << res.message();
}
}
};
}