Files
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

763 lines
33 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/AssemblyKernel.hpp>
#include <rocRoller/CodeGen/ArgumentLoader.hpp>
#include <rocRoller/CodeGen/CopyGenerator.hpp>
#include <rocRoller/CodeGen/MemoryInstructions.hpp>
#include <rocRoller/CommandSolution.hpp>
#include <rocRoller/Context.hpp>
#include <rocRoller/Operations/Command.hpp>
#include <rocRoller/TensorDescriptor.hpp>
#include "GPUContextFixture.hpp"
#include "GenericContextFixture.hpp"
#include "Utilities.hpp"
#include <common/GEMMProblem.hpp>
using namespace rocRoller;
namespace rocRollerTest
{
struct ConversionSettings
{
ConversionSettings() = delete;
ConversionSettings(size_t nx, size_t ny, int m, int n, int t_m, int t_n)
: nx(nx)
, ny(ny)
, m(m)
, n(n)
, threadTileM(t_m)
, threadTileN(t_n)
{
AssertFatal(m > 0 && n > 0 && threadTileM > 0 && threadTileN > 0,
"Invalid Test Dimensions");
unsigned int workgroup_size_x = m / threadTileM;
unsigned int workgroup_size_y = n / threadTileN;
AssertFatal((size_t)m * n
== threadTileM * threadTileN * workgroup_size_x * workgroup_size_y,
"MacroTile size mismatch");
// TODO: Handle when thread tiles include out of range indices
AssertFatal(nx % threadTileM == 0, "Thread tile size must divide tensor size");
AssertFatal(ny % threadTileN == 0, "Thread tile size must divide tensor size");
}
template <typename SrcType>
auto generateData(unsigned const seed = 129674u) const
{
RandomGenerator random(seed);
return random.vector<SrcType>(nx * ny, -100.0, 100.0);
}
size_t nx; //> tensor size x
size_t ny; //> tensor size y
int m; //> macro tile size x
int n; //> macro tile size y
int threadTileM; //> thread tile size x
int threadTileN; //> thread tile size y
};
class ConversionTest : public CurrentGPUContextFixture
{
public:
/*
* Testing: D = Convert(A * B + C)
*/
template <typename TypeAB, typename TypeC, typename TypeD>
void matrixMultiplyABC(
int wave_m, int wave_n, int wave_k, int wave_b, double acceptableError);
/*
* Testing: D = Convert(A * B)
*/
template <typename TypeAB, typename TypeD>
void matrixMultiply(int wave_m, int wave_n, int wave_k, int wave_b, double acceptableError);
/*
* Testing: C = Convert(A) + Convert(B)
*/
template <typename DestType, typename SrcType>
void convertAdd(std::vector<SrcType>& a,
std::vector<SrcType>& b,
ConversionSettings& cs,
bool const loadLDS_A);
/*
* Testing: C = Convert(A)
*/
template <typename DestType, typename SrcType>
void convertTo(std::vector<SrcType>& srcData,
ConversionSettings const& cs,
bool const loadLDS_A,
std::optional<uint32_t> const seed = std::nullopt);
};
template <typename TypeAB, typename TypeC, typename TypeD>
void ConversionTest::matrixMultiplyABC(
int wave_m, int wave_n, int wave_k, int wave_b, double acceptableError)
{
// Need to use mfma instructions to calculate (A * B) + C and then convert the
// result into D
// e.g., D (Half) = A (Float) * B (Float) + C (Float)
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA);
// matrix size: A is MxK; B is KxN; D is MxN
int M = 512;
int N = 512;
int K = 256;
// output macro tile size
int mac_m = 2 * wave_m;
int mac_n = 2 * 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)");
uint workgroup_size_x = 256;
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);
RandomGenerator random(61u);
auto A = random.vector<TypeAB>(M * K, -1.f, 1.f);
auto B = random.vector<TypeAB>(K * N, -1.f, 1.f);
auto C = random.vector<TypeC>(M * N, -1.f, 1.f);
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<TypeD>(M * N);
auto command = std::make_shared<Command>();
auto const dataTypeAB = TypeInfo<TypeAB>::Var.dataType;
auto const dataTypeC = TypeInfo<TypeC>::Var.dataType;
auto const dataTypeD = TypeInfo<TypeD>::Var.dataType;
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeAB)); // A
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeAB)); // B
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
auto tagTensorC = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeC)); // C
auto tagLoadC = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorC));
auto tagAB
= command->addOperation(rocRoller::Operations::T_Mul(tagLoadA, tagLoadB)); // A * B
auto execute = rocRoller::Operations::T_Execute(command->getNextTag());
auto tagAdd = execute.addXOp(rocRoller::Operations::E_Add(tagAB, tagLoadC)); // A * B + C
command->addOperation(std::move(execute));
auto cvtOp = rocRoller::Operations::T_Execute(command->getNextTag()); // Convert(A * B + C)
auto tagCvt = cvtOp.addXOp(rocRoller::Operations::E_Cvt(tagAdd, dataTypeD));
command->addOperation(std::move(cvtOp));
auto tagTensorD = command->addOperation(
rocRoller::Operations::Tensor(2, dataTypeD)); // D = Convert(A * B + C)
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagCvt, tagTensorD));
CommandArguments commandArgs = command->createArguments();
TensorDescriptor descA(dataTypeAB, {size_t(M), size_t(K)}, "N");
TensorDescriptor descB(dataTypeAB, {size_t(K), size_t(N)}, "N");
TensorDescriptor descC(dataTypeC, {size_t(M), size_t(N)}, "N");
TensorDescriptor descD(dataTypeD, {size_t(M), size_t(N)}, "N");
setCommandTensorArg(commandArgs, tagTensorA, descA, d_A.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, d_B.get());
setCommandTensorArg(commandArgs, tagTensorC, descC, d_C.get());
setCommandTensorArg(commandArgs, tagTensorD, descD, d_D.get());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
params->setManualWavefrontCount({2u, 2u});
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});
auto macTileB = KernelGraph::CoordinateGraph::MacroTile(
{mac_k, mac_n}, LayoutType::MATRIX_B, {wave_m, wave_n, wave_k, wave_b});
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);
CommandKernel commandKernel(command, "MatrixMultiplyABC");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<TypeD> gpu_D(M * N);
ASSERT_THAT(hipMemcpy(gpu_D.data(), d_D.get(), M * N * sizeof(TypeD), hipMemcpyDefault),
HasHipSuccess(0));
std::vector<float> tmp_D(M * N, 0.f);
CPUMM(tmp_D, C, A, B, M, N, K, 1.0, 1.0, false, false);
std::vector<TypeD> cpu_D;
cpu_D.reserve(M * N);
for(size_t i = 0; i < M * N; i++)
cpu_D.emplace_back(TypeD(tmp_D[i]));
auto tol = gemmAcceptableError<TypeAB, TypeAB, TypeD>(
M, N, K, m_context->targetArchitecture().target());
auto res = compare(gpu_D, cpu_D, tol);
Log::info("MatrixMultiplyABC and Conversion RNorm is {}", res.relativeNormL2);
ASSERT_TRUE(res.ok) << res.message();
}
template <typename TypeAB, typename TypeD>
void ConversionTest::matrixMultiply(
int wave_m, int wave_n, int wave_k, int wave_b, double acceptableError)
{
// matrix size: A is MxK; B is KxN; D is MxN
int M = 512;
int N = 512;
int K = 256;
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);
RandomGenerator random(61u);
auto A = random.vector<TypeAB>(M * K, -1.f, 1.f);
auto B = random.vector<TypeAB>(K * N, -1.f, 1.f);
auto d_A = make_shared_device(A);
auto d_B = make_shared_device(B);
auto d_D = make_shared_device<TypeD>(M * N);
auto command = std::make_shared<Command>();
auto const dataTypeAB = TypeInfo<TypeAB>::Var.dataType;
auto const dataTypeD = TypeInfo<TypeD>::Var.dataType;
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeAB)); // A
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB = command->addOperation(rocRoller::Operations::Tensor(2, dataTypeAB)); // B
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
auto tagAB
= command->addOperation(rocRoller::Operations::T_Mul(tagLoadA, tagLoadB)); // A * B
auto cvtOp = rocRoller::Operations::T_Execute(command->getNextTag()); // Convert(A * B)
auto tagCvt = cvtOp.addXOp(rocRoller::Operations::E_Cvt(tagAB, dataTypeD));
command->addOperation(std::move(cvtOp));
auto tagTensorD = command->addOperation(
rocRoller::Operations::Tensor(2, dataTypeD)); // D = Convert(A * B)
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagCvt, tagTensorD));
CommandArguments commandArgs = command->createArguments();
TensorDescriptor descA(dataTypeAB, {size_t(M), size_t(K)}, "N");
TensorDescriptor descB(dataTypeAB, {size_t(K), size_t(N)}, "N");
TensorDescriptor descD(dataTypeD, {size_t(M), size_t(N)}, "N");
setCommandTensorArg(commandArgs, tagTensorA, descA, d_A.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, d_B.get());
setCommandTensorArg(commandArgs, tagTensorD, descD, d_D.get());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
params->setManualWavefrontCount({wavefrontCountX, wavefrontCountY});
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
auto macTileA = KernelGraph::CoordinateGraph::MacroTile(
{mac_m, mac_k}, LayoutType::MATRIX_A, {wave_m, wave_n, wave_k, wave_b});
auto macTileB = KernelGraph::CoordinateGraph::MacroTile(
{mac_k, mac_n}, LayoutType::MATRIX_B, {wave_m, wave_n, wave_k, wave_b});
params->setDimensionInfo(tagLoadA, macTileA);
params->setDimensionInfo(tagLoadB, macTileB);
CommandKernel commandKernel(command, "MatrixMultiply");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<TypeD> gpu_D(M * N, 0.f);
ASSERT_THAT(hipMemcpy(gpu_D.data(), d_D.get(), M * N * sizeof(TypeD), hipMemcpyDefault),
HasHipSuccess(0));
std::vector<TypeAB> tmp_D(M * N, 0.f);
CPUMM(tmp_D, tmp_D, A, B, M, N, K, 1.0, 1.0, false, false);
std::vector<TypeD> cpu_D;
cpu_D.reserve(M * N);
for(size_t i = 0; i < M * N; i++)
cpu_D.emplace_back(TypeD(tmp_D[i]));
auto tol = gemmAcceptableError<TypeAB, TypeAB, TypeD>(
M, N, K, m_context->targetArchitecture().target());
auto res = compare(gpu_D, cpu_D, tol);
Log::info("D = Convert(A * B) RNorm is {}", res.relativeNormL2);
ASSERT_TRUE(res.ok) << res.message();
}
template <typename DestType, typename SrcType>
void ConversionTest::convertAdd(std::vector<SrcType>& a,
std::vector<SrcType>& b,
ConversionSettings& cs,
bool const loadLDS_A)
{
static_assert(!std::is_same_v<DestType, SrcType>,
"Source and destination types for conversion must be different");
auto command = std::make_shared<Command>();
auto const srcDataType = TypeInfo<SrcType>::Var.dataType;
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(2, srcDataType));
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto tagTensorB = command->addOperation(rocRoller::Operations::Tensor(2, srcDataType));
auto tagLoadB = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorB));
auto const destDataType = TypeInfo<DestType>::Var.dataType;
auto execute = rocRoller::Operations::T_Execute(command->getNextTag());
auto tagCvtA
= execute.addXOp(rocRoller::Operations::E_Cvt(tagLoadA, destDataType)); // Convert A
auto tagCvtB
= execute.addXOp(rocRoller::Operations::E_Cvt(tagLoadB, destDataType)); // Convert B
auto tagC = execute.addXOp(
rocRoller::Operations::E_Add(tagCvtA, tagCvtB)); // C = converted(A) + converted(B)
command->addOperation(std::move(execute));
auto tagTensorC = command->addOperation(rocRoller::Operations::Tensor(2, destDataType));
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagC, tagTensorC));
CommandArguments commandArgs = command->createArguments();
auto d_a = make_shared_device(a);
auto d_b = make_shared_device(b);
auto d_c = make_shared_device<DestType>(a.size());
TensorDescriptor descA(srcDataType, {size_t(cs.nx), size_t(cs.ny)}, "T");
TensorDescriptor descB(srcDataType, {size_t(cs.nx), size_t(cs.ny)}, "T");
TensorDescriptor descC(destDataType, {size_t(cs.nx), size_t(cs.ny)}, "T");
setCommandTensorArg(commandArgs, tagTensorA, descA, d_a.get());
setCommandTensorArg(commandArgs, tagTensorB, descB, d_b.get());
setCommandTensorArg(commandArgs, tagTensorC, descC, d_c.get());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
auto macTileVGPR = KernelGraph::CoordinateGraph::MacroTile({cs.m, cs.n},
LayoutType::ROW_MAJOR,
{cs.threadTileM, cs.threadTileN},
MemoryType::VGPR);
auto macTileLDS = KernelGraph::CoordinateGraph::MacroTile(
{cs.m, cs.n}, LayoutType::ROW_MAJOR, {cs.threadTileM, cs.threadTileN}, MemoryType::LDS);
unsigned int workgroup_size_x = cs.m / cs.threadTileM;
unsigned int workgroup_size_y = cs.n / cs.threadTileN;
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
params->setDimensionInfo(tagLoadA, loadLDS_A ? macTileLDS : macTileVGPR);
params->setDimensionInfo(tagLoadB, macTileVGPR);
// TODO Fix MemoryType promotion (LDS)
params->setDimensionInfo(tagC, macTileVGPR);
// each workgroup will get one tile; since workgroup_size matches m * n
auto NX = std::make_shared<Expression::Expression>(
cs.nx / cs.threadTileM); // number of work items x
auto NY = std::make_shared<Expression::Expression>(
cs.ny / cs.threadTileN); // number of work items y
auto NZ = std::make_shared<Expression::Expression>(1u); // number of work items z
CommandKernel commandKernel(command, "convertAndAdd");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<DestType> gpuResult(a.size());
ASSERT_THAT(
hipMemcpy(
gpuResult.data(), d_c.get(), gpuResult.size() * sizeof(DestType), hipMemcpyDefault),
HasHipSuccess(0));
// Reference result generated on CPU
std::vector<DestType> cpuResult;
cpuResult.reserve(gpuResult.size());
for(size_t i = 0; i < a.size(); i++)
cpuResult.emplace_back(DestType(a[i]) + DestType(b[i]));
auto tol = AcceptableError{epsilon<double>(), "Should be exact."};
auto res = compare(gpuResult, cpuResult, tol);
EXPECT_TRUE(res.ok) << res.message();
Log::info("C = Convert(A) + Convert(B) RNorm is {}", res.relativeNormL2);
}
template <typename DestType, typename SrcType>
void ConversionTest::convertTo(std::vector<SrcType>& srcData,
ConversionSettings const& cs,
bool const loadLDS_A,
std::optional<uint32_t> const seed)
{
static_assert(!std::is_same_v<DestType, SrcType>,
"Source and destination types for conversion must be different");
unsigned int workgroup_size_x = cs.m / cs.threadTileM;
unsigned int workgroup_size_y = cs.n / cs.threadTileN;
// each workgroup will get one tile; since workgroup_size matches m * n
auto NX = std::make_shared<Expression::Expression>(
cs.nx / cs.threadTileM); // number of work items x
auto NY = std::make_shared<Expression::Expression>(
cs.ny / cs.threadTileN); // number of work items y
auto NZ = std::make_shared<Expression::Expression>(1u); // number of work items z
auto command = std::make_shared<Command>();
auto const srcDataType = TypeInfo<SrcType>::Var.dataType;
auto tagTensorA = command->addOperation(rocRoller::Operations::Tensor(2, srcDataType));
auto tagLoadA = command->addOperation(rocRoller::Operations::T_Load_Tiled(tagTensorA));
auto const destDataType = TypeInfo<DestType>::Var.dataType;
Operations::OperationTag tagSeed, tagLoadSeed;
if(seed.has_value())
{
tagSeed = command->addOperation(rocRoller::Operations::Scalar(DataType::UInt32));
tagLoadSeed = command->addOperation(rocRoller::Operations::T_Load_Scalar(tagSeed));
}
auto execute = rocRoller::Operations::T_Execute(command->getNextTag());
// Convert A to destination type
auto tagCvtA = seed.has_value()
? execute.addXOp(rocRoller::Operations::E_StochasticRoundingCvt(
tagLoadA, tagLoadSeed, destDataType))
: execute.addXOp(rocRoller::Operations::E_Cvt(tagLoadA, destDataType));
command->addOperation(std::move(execute));
auto tagTensorC = command->addOperation(rocRoller::Operations::Tensor(2, destDataType));
command->addOperation(rocRoller::Operations::T_Store_Tiled(tagCvtA, tagTensorC));
CommandArguments commandArgs = command->createArguments();
auto d_a = make_shared_device(srcData);
TensorDescriptor descA(srcDataType, {size_t(cs.nx), size_t(cs.ny)}, "T");
setCommandTensorArg(commandArgs, tagTensorA, descA, d_a.get());
auto d_c = make_shared_device<DestType>(srcData.size());
TensorDescriptor descC(destDataType, {size_t(cs.nx), size_t(cs.ny)}, "T");
setCommandTensorArg(commandArgs, tagTensorC, descC, d_c.get());
if(seed.has_value())
commandArgs.setArgument(tagSeed, ArgumentType::Value, seed.value());
auto params = std::make_shared<CommandParameters>();
params->setManualKernelDimension(2);
params->setManualWorkgroupSize({workgroup_size_x, workgroup_size_y, 1});
auto macTileVGPR = KernelGraph::CoordinateGraph::MacroTile({cs.m, cs.n},
LayoutType::ROW_MAJOR,
{cs.threadTileM, cs.threadTileN},
MemoryType::VGPR);
auto macTileLDS = KernelGraph::CoordinateGraph::MacroTile(
{cs.m, cs.n}, LayoutType::ROW_MAJOR, {cs.threadTileM, cs.threadTileN}, MemoryType::LDS);
params->setDimensionInfo(tagLoadA, loadLDS_A ? macTileLDS : macTileVGPR);
// TODO Fix MemoryType promotion (LDS)
params->setDimensionInfo(tagCvtA, macTileVGPR);
CommandKernel commandKernel(command, "DirectConvert");
commandKernel.setContext(m_context);
commandKernel.setCommandParameters(params);
commandKernel.generateKernel();
commandKernel.launchKernel(commandArgs.runtimeArguments());
std::vector<DestType> gpuResult(srcData.size());
ASSERT_THAT(
hipMemcpy(
gpuResult.data(), d_c.get(), gpuResult.size() * sizeof(DestType), hipMemcpyDefault),
HasHipSuccess(0));
// Reference result generated on CPU
std::vector<DestType> cpuResult;
cpuResult.reserve(gpuResult.size());
for(auto v : srcData)
{
if(not seed.has_value())
{
cpuResult.emplace_back(DestType(v));
}
else
{
if constexpr(std::is_same_v<DestType, FP8> || std::is_same_v<DestType, BF8>)
{
int constexpr exp_width = std::is_same_v<DestType, FP8> ? 4 : 5;
int constexpr mantissa_width = 7 - exp_width;
cpuResult.emplace_back(0);
bool constexpr is_bf8 = std::is_same_v<DestType, BF8>;
auto const f8Mode = Settings::getInstance()->get(Settings::F8ModeOption);
if(f8Mode == rocRoller::F8Mode::NaNoo)
{
cpuResult.back().data = DataTypes::cast_to_f8<mantissa_width,
exp_width,
float,
false /* is_ocp */,
is_bf8,
true /*negative_zero_nan*/,
true /*clip*/>(
v /* value to be converted */,
true /* is stochastic rounding? */,
seed.value() /* seed for stochastic rounding */);
}
else
{
cpuResult.back().data = DataTypes::cast_to_f8<mantissa_width,
exp_width,
float,
true /* is_ocp */,
is_bf8,
true /*negative_zero_nan*/,
true /*clip*/>(
v /* value to be converted */,
true /* is stochastic rounding? */,
seed.value() /* seed for stochastic rounding */);
}
}
else
{
AssertFatal(true, "Destionation tyoe of SR conversion can only be FP8/BF8");
}
}
}
auto tol = AcceptableError{epsilon<double>(), "Should be exact."};
auto res = compare(gpuResult, cpuResult, tol);
EXPECT_TRUE(res.ok) << res.message();
Log::info("C = Convert(A) RNorm is {}", res.relativeNormL2);
}
TEST_F(ConversionTest, GPU_FloatToFP8_VGPR)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::FP8>(srcData, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToFP8_LDS)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::FP8>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToBF8_VGPR)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::BF8>(srcData, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToBF8_LDS)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::BF8>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToHalf_VGPR)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::Half>(srcData, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToHalf_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::Half>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_HalfToFloat_VGPR)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<Half>();
convertTo<float>(srcData, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_HalfToFloat_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<Half>();
convertTo<float>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_BF16ToFloat_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<BFloat16>();
convertTo<float>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_FloatToBF16_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<BFloat16>(srcData, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_AddFloatToHalf_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto a = cs.generateData<float>(12345u);
auto b = cs.generateData<float>(56789u);
// C (Half) = Convert( A(Float) ) + Convert( B(Float) )
convertAdd<rocRoller::Half>(a, b, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_AddFloatToHalf_VGPR)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto a = cs.generateData<float>(12345u);
auto b = cs.generateData<float>(56789u);
// C (Half) = Convert( A(Float) ) + Convert( B(Float) )
convertAdd<rocRoller::Half>(a, b, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_AddHalfToFloat_LDS)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto a = cs.generateData<Half>(12345u);
auto b = cs.generateData<Half>(56789u);
// C (Float) = Convert( A(Half) ) + Convert( B(Half) )
convertAdd<float>(a, b, cs, true /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_AddHalfToFloat_VGPR)
{
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto a = cs.generateData<Half>(12345u);
auto b = cs.generateData<Half>(56789u);
// C (Float) = Convert( A(Half) ) + Convert( B(Half) )
convertAdd<float>(a, b, cs, false /* load A in LDS */);
}
TEST_F(ConversionTest, GPU_MatrixMultiplyABC_F32_Half)
{
// D (Half) = Convert( A (F32) * B (F32) + C (F32) )
matrixMultiplyABC<float, float, Half>(32, 32, 2, 1, 2.e-6);
}
TEST_F(ConversionTest, GPU_MatrixMultiplyABC_F32_FP8)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
// D (FP8) = Convert( A (FP8) * B (FP8) + C (F32) )
matrixMultiplyABC<FP8, float, FP8>(16, 16, 32, 1, 2.e-6);
}
TEST_F(ConversionTest, GPU_MatrixMultiplyABC_F32_BF8)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
// D (BF8) = Convert( A (BF8) * B (BF8) + C (F32) )
matrixMultiplyABC<BF8, float, BF8>(16, 16, 32, 1, 2.e-6);
}
TEST_F(ConversionTest, GPU_MatrixMultiply_MFMA)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA);
// D (Half) = Convert( A (F32) * B (F32) )
matrixMultiply<float, Half>(32, 32, 2, 1, 2.e-6);
}
TEST_F(ConversionTest, GPU_MatrixMultiply_WMMA)
{
REQUIRE_ARCH_CAP(GPUCapability::HasWMMA_f32_16x16x16_f16);
// Note: the output type of A(Half) * B(Half) is Float
// D (Half) = Convert( A (Half) * B (Half) )
matrixMultiply<Half, Half>(16, 16, 16, 1, 2.e-6);
}
TEST_F(ConversionTest, GPU_SR_Float2FP8_VGPR)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::FP8>(
srcData, cs, false /* load A in LDS */, 12345u /* seed for SR conversion */);
}
TEST_F(ConversionTest, GPU_SR_Float2BF8_VGPR)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::BF8>(
srcData, cs, false /* load A in LDS */, 12345u /* seed for SR conversion */);
}
TEST_F(ConversionTest, GPU_SR_Float2FP8_LDS)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::FP8>(
srcData, cs, true /* load A in LDS */, 12345u /* seed for SR conversion */);
}
TEST_F(ConversionTest, GPU_SR_Float2BF8_LDS)
{
REQUIRE_ARCH_CAP(GPUCapability::HasMFMA_fp8);
ConversionSettings cs(256, 512, 16, 8, 4, 4);
auto srcData = cs.generateData<float>();
convertTo<rocRoller::BF8>(
srcData, cs, true /* load A in LDS */, 12345u /* seed for SR conversion */);
}
}