Files
TheRock/rocm-libraries/dnn-providers/hipblaslt-provider/tests/engines/plans/TestHipblasltMxMatmulPlan.cpp
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

230 lines
9.5 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <hipblaslt/hipblaslt.h>
#include <hipdnn_flatbuffers_sdk/data_objects/block_scale_dequantize_attributes_generated.h>
#include <hipdnn_flatbuffers_sdk/data_objects/data_types_generated.h>
#include <hipdnn_flatbuffers_sdk/data_objects/matmul_attributes_generated.h>
#include <hipdnn_flatbuffers_sdk/flatbuffer_utilities/GraphWrapper.hpp>
#include <hipdnn_plugin_sdk/PluginException.hpp>
#include <hipdnn_test_sdk/utilities/DeviceQuery.hpp>
#include <hipdnn_test_sdk/utilities/FlatbufferGraphTestUtils.hpp>
#include <hipdnn_test_sdk/utilities/TestUtilities.hpp>
#include "HipdnnEnginePluginHandle.hpp"
#include "engines/plans/HipblasltMxMatmulPlan.hpp"
#include "engines/plans/MxArchSupport.hpp"
using namespace hipblaslt_plugin;
using namespace hipdnn_flatbuffers_sdk::flatbuffer_utilities;
namespace
{
using DT = hipdnn_flatbuffers_sdk::data_objects::DataType;
using hipdnn_test_sdk::utilities::createValidMxMatmulGraph;
/// Borrowed references to the three node attribute tables of an MX graph.
/// node 0 → dequant A, node 1 → dequant B, node 2 → matmul (emission order of
/// createValidMxMatmulGraph with swapDequantOrder=false).
struct MxNodeAttrs
{
const hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes& deqA;
const hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes& deqB;
const hipdnn_flatbuffers_sdk::data_objects::MatmulAttributes& matmul;
};
MxNodeAttrs getMxNodeAttrs(const GraphWrapper& graph)
{
return MxNodeAttrs{
graph.getNodeWrapper(0)
.attributesAs<hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes>(),
graph.getNodeWrapper(1)
.attributesAs<hipdnn_flatbuffers_sdk::data_objects::BlockScaleDequantizeAttributes>(),
graph.getNodeWrapper(2)
.attributesAs<hipdnn_flatbuffers_sdk::data_objects::MatmulAttributes>()};
}
/// Query a single scalar attribute from a hipBLASLt matmul descriptor.
template <typename T>
T getDescAttribute(const HipblasltMatmulDesc& desc, hipblasLtMatmulDescAttributes_t attr)
{
T value{};
size_t sizeWritten = 0;
EXPECT_EQ(hipblasLtMatmulDescGetAttribute(
desc.matmulDesc(), attr, static_cast<void*>(&value), sizeof(value), &sizeWritten),
HIPBLAS_STATUS_SUCCESS);
EXPECT_EQ(sizeWritten, sizeof(value));
return value;
}
} // anonymous namespace
// ===========================================================================
// MxMatmulParams (CPU — host-side hipBLASLt layout/descriptor only, no device)
// ===========================================================================
// MxMatmulParams stores operands in hipBLAS's frame: the row-major swap maps our
// B to hipBLAS A and our A to hipBLAS B. So a() traces back to dequant B's X
// tensor, b() to dequant A's X tensor, and c() to the matmul output.
TEST(TestMxMatmulParams, LayoutUidsMatchInputTensors)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams const params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_EQ(params.a().uid(), attrs.deqB.x_tensor_uid()); // hipBLAS A <- our B
EXPECT_EQ(params.b().uid(), attrs.deqA.x_tensor_uid()); // hipBLAS B <- our A
EXPECT_EQ(params.c().uid(), attrs.matmul.c_tensor_uid());
}
// Scale UIDs follow the same hipBLAS-frame swap (aScaleUid = our B's scale,
// bScaleUid = our A's scale), and both scale modes must be set to VEC32_UE8M0
// (the OCP MX block-scale mode).
TEST(TestMxMatmulParams, ScaleUidsAndModeWiring)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams const params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_EQ(params.aScaleUid(), attrs.deqB.scale_tensor_uid()); // hipBLAS A_SCALE <- our B
EXPECT_EQ(params.bScaleUid(), attrs.deqA.scale_tensor_uid()); // hipBLAS B_SCALE <- our A
EXPECT_NE(params.aScaleUid(), params.bScaleUid());
EXPECT_EQ(getDescAttribute<hipblasLtMatmulMatrixScale_t>(params.desc(),
HIPBLASLT_MATMUL_DESC_A_SCALE_MODE),
HIPBLASLT_MATMUL_MATRIX_SCALE_VEC32_UE8M0);
EXPECT_EQ(getDescAttribute<hipblasLtMatmulMatrixScale_t>(params.desc(),
HIPBLASLT_MATMUL_DESC_B_SCALE_MODE),
HIPBLASLT_MATMUL_MATRIX_SCALE_VEC32_UE8M0);
}
// Default MX shape: A is col-major (opA=T), B is row-major (opB=N). Because of
// the row-major BLAS swap, the descriptor is built as
// (transA=getTrans(B), transB=getTrans(A)) → (OP_N, OP_T).
TEST(TestMxMatmulParams, TransposeInferenceDefault)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams const params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSA),
HIPBLAS_OP_N);
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSB),
HIPBLAS_OP_T);
}
// When A is row-major instead of col-major, getTrans(A) flips to OP_N, so the
// descriptor's transB (= getTrans(A)) becomes OP_N. transA (= getTrans(B)) is
// unchanged.
TEST(TestMxMatmulParams, TransposeInferenceRowMajorA)
{
// Row-major A (strides {128,1}) instead of the default col-major {1,32}.
auto fb = createValidMxMatmulGraph(
{32, 128}, {128, 1}, {128, 32}, {32, 1}, {32, 32}, {32, 1}, {32, 4}, {4, 32});
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams const params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSA),
HIPBLAS_OP_N);
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSB),
HIPBLAS_OP_N);
}
// When B is col-major instead of row-major, getTrans(B) flips to OP_T, so the
// descriptor's transA (= getTrans(B)) becomes OP_T. transB (= getTrans(A)) is
// unchanged.
TEST(TestMxMatmulParams, TransposeInferenceColMajorB)
{
// Col-major B (strides {1,128}) instead of the default row-major {32,1}.
auto fb = createValidMxMatmulGraph(
{32, 128}, {1, 32}, {128, 32}, {1, 128}, {32, 32}, {32, 1}, {32, 4}, {4, 32});
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams const params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSA),
HIPBLAS_OP_T);
EXPECT_EQ(getDescAttribute<hipblasOperation_t>(params.desc(), HIPBLASLT_MATMUL_DESC_TRANSB),
HIPBLAS_OP_T);
}
// ===========================================================================
// MxMatmulPlan (GPU — requires a device and a live hipblasLt handle)
// ===========================================================================
class TestGpuMxMatmulPlan : public ::testing::Test
{
protected:
void SetUp() override
{
SKIP_IF_NO_DEVICES();
const auto archName = hipdnn_test_sdk::utilities::currentDeviceArch();
if(!hipblaslt_plugin_test::isMxSupportedArch(archName))
{
GTEST_SKIP() << "MX block-scaled GEMM is not supported on " << archName;
}
ASSERT_EQ(hipblasLtCreate(&_handle.hipblasltHandle), HIPBLAS_STATUS_SUCCESS);
}
void TearDown() override
{
if(_handle.hipblasltHandle != nullptr)
{
EXPECT_EQ(hipblasLtDestroy(_handle.hipblasltHandle), HIPBLAS_STATUS_SUCCESS);
}
}
HipdnnEnginePluginHandle _handle;
};
TEST_F(TestGpuMxMatmulPlan, CreatesPlanWithValidGraph)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
EXPECT_NO_THROW(MxMatmulPlan(_handle, std::move(params)));
}
TEST_F(TestGpuMxMatmulPlan, PlanReturnsValidWorkspaceSize)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
MxMatmulPlan const plan(_handle, std::move(params));
EXPECT_GE(plan.getWorkspaceSize(_handle), 0u);
}
// execute() must reject a null workspace: MX GEMM always reports a non-zero
// workspace size (the reserved scale-transpose region), so a null pointer is a
// caller contract violation. The check runs before any device-buffer access, so
// no real buffers are needed here.
TEST_F(TestGpuMxMatmulPlan, ExecuteThrowsOnNullWorkspace)
{
auto fb = createValidMxMatmulGraph();
GraphWrapper const graph(fb.GetBufferPointer(), fb.GetSize());
const auto attrs = getMxNodeAttrs(graph);
MxMatmulParams params(attrs.deqA, attrs.deqB, attrs.matmul, graph.getTensorMap());
MxMatmulPlan const plan(_handle, std::move(params));
EXPECT_THROW(plan.execute(_handle, nullptr, 0, nullptr),
hipdnn_plugin_sdk::HipdnnPluginException);
}