// Copyright © Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include "HipdnnMiopenHandle.hpp" #include "engines/plans/MiopenBatchnormFwdTrainingPlanBuilder.hpp" #include "mocks/MockHipdnnMiopenContext.hpp" using namespace miopen_plugin; using namespace hipdnn_test_sdk::utilities; using namespace hipdnn_flatbuffers_sdk::flatbuffer_utilities; class TestMiopenBatchnormFwdTrainingActivPlanBuilder : public ::testing::Test { protected: void SetUp() override { SKIP_IF_NO_DEVICES(); _dummyHandle = std::make_unique(); } MiopenBatchnormFwdTrainingPlanBuilder _planBuilder; std::unique_ptr _dummyHandle; }; TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsTrueForValidSingleNodeGraph) { auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingGraph(); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_TRUE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsFalseForThreeNodeGraph) { const MockGraph mockGraph; EXPECT_CALL(mockGraph, nodeCount()).WillRepeatedly(::testing::Return(3)); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, mockGraph); EXPECT_FALSE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsTrueForValidTwoNodeGraph) { auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph(); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_TRUE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsTrueForFusionWithoutRunningStatistics) { // Create a fused batchnorm training + activation graph WITHOUT running statistics auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph(); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_TRUE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsFalseForNonReluActivation) { // Graph with SIGMOID_FWD instead of RELU_FWD auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph( false, false, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::SIGMOID_FWD); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_FALSE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, GetWorkspaceSizeReturnsZero) { auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph(); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const HipdnnMiopenSettings settings; const size_t workspaceSize = _planBuilder.getMaxWorkspaceSize(*_dummyHandle, graph, settings); EXPECT_EQ(workspaceSize, 0u); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, BuildPlanSetsPlanForValidGraph) { auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph(); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const MockEngineConfig mockEngineConfig; HipdnnMiopenContext ctx; EXPECT_NO_THROW(_planBuilder.buildPlan(*_dummyHandle, graph, mockEngineConfig, ctx)); EXPECT_TRUE(ctx.hasValidPlan()); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, BuildPlanThrowsForMalformedBatchnormAttributes) { // Create graph with batchnorm training node but null attributes flatbuffers::FlatBufferBuilder builder; const std::vector<::flatbuffers::Offset> tensorAttributes; std::vector<::flatbuffers::Offset> nodes; // Node with BatchnormAttributes type but null attributes pointer auto node = hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "malformed_bn_training", hipdnn_flatbuffers_sdk::data_objects::DataType::UNSET, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::BatchnormAttributes, 0); nodes.push_back(node); auto actAttr = hipdnn_flatbuffers_sdk::data_objects::CreatePointwiseAttributes( builder, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::RELU_FWD, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 2, flatbuffers::nullopt, flatbuffers::nullopt, 3); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "act", hipdnn_flatbuffers_sdk::data_objects::DataType::UNSET, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::PointwiseAttributes, actAttr.Union())); auto graphOffset = hipdnn_flatbuffers_sdk::data_objects::CreateGraphDirect( builder, "test", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::DataType::HALF, hipdnn_flatbuffers_sdk::data_objects::DataType::BFLOAT16, &tensorAttributes, &nodes); builder.Finish(graphOffset); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const MockEngineConfig mockEngineConfig; HipdnnMiopenContext ctx; EXPECT_THROW(_planBuilder.buildPlan(*_dummyHandle, graph, mockEngineConfig, ctx), std::invalid_argument); EXPECT_FALSE(ctx.hasValidPlan()); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, BuildPlanThrowsForMalformedActivationAttributes) { // Create graph with valid batchnorm but malformed activation flatbuffers::FlatBufferBuilder builder; std::vector<::flatbuffers::Offset> tensorAttributes; const std::vector strides = {1, 3, 14, 14}; const std::vector dims = {1, 3, 14, 14}; const std::vector derivedStrides = {1, 3, 1, 1}; const std::vector derivedDims = {1, 3, 1, 1}; tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 1, "x", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &strides, &dims)); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 2, "y_virtual", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &strides, &dims, true)); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 3, "scale", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &derivedStrides, &derivedDims)); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 4, "bias", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &derivedStrides, &derivedDims)); const hipdnn_flatbuffers_sdk::data_objects::Float32Value epsilonVal(1e-5f); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributes( builder, 5, builder.CreateString("epsilon"), hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, 0, 0, false, hipdnn_flatbuffers_sdk::data_objects::TensorValue::Float32Value, builder.CreateStruct(epsilonVal).Union())); std::vector<::flatbuffers::Offset> nodes; // Valid batchnorm training node auto bnormAttributes = hipdnn_flatbuffers_sdk::data_objects::CreateBatchnormAttributes(builder, 1, 3, 4, 5, 0, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 2, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "bn_training", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::BatchnormAttributes, bnormAttributes.Union())); // Malformed activation (null attributes) nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "malformed_act", hipdnn_flatbuffers_sdk::data_objects::DataType::UNSET, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::PointwiseAttributes, 0)); auto graphOffset = hipdnn_flatbuffers_sdk::data_objects::CreateGraphDirect( builder, "test", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::DataType::HALF, hipdnn_flatbuffers_sdk::data_objects::DataType::BFLOAT16, &tensorAttributes, &nodes); builder.Finish(graphOffset); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const MockEngineConfig mockEngineConfig; HipdnnMiopenContext ctx; EXPECT_THROW(_planBuilder.buildPlan(*_dummyHandle, graph, mockEngineConfig, ctx), std::invalid_argument); EXPECT_FALSE(ctx.hasValidPlan()); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsFalseForWrongNodeOrder) { // Create graph with nodes in wrong order (activation → batchnorm instead of batchnorm → activation) flatbuffers::FlatBufferBuilder builder; std::vector<::flatbuffers::Offset> tensorAttributes; std::vector<::flatbuffers::Offset> nodes; // Wrong order: activation first auto actAttr = hipdnn_flatbuffers_sdk::data_objects::CreatePointwiseAttributes( builder, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::RELU_FWD, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 1, flatbuffers::nullopt, flatbuffers::nullopt, 2); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "act", hipdnn_flatbuffers_sdk::data_objects::DataType::UNSET, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::PointwiseAttributes, actAttr.Union())); // Then batchnorm (wrong order!) const hipdnn_flatbuffers_sdk::data_objects::Float32Value epsilonVal(1e-5f); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributes( builder, 5, builder.CreateString("epsilon"), hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, 0, 0, false, hipdnn_flatbuffers_sdk::data_objects::TensorValue::Float32Value, builder.CreateStruct(epsilonVal).Union())); auto bnormAttributes = hipdnn_flatbuffers_sdk::data_objects::CreateBatchnormAttributes(builder, 1, 3, 4, 5, 0, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 2, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "bn", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::BatchnormAttributes, bnormAttributes.Union())); auto graphOffset = hipdnn_flatbuffers_sdk::data_objects::CreateGraphDirect( builder, "test", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::DataType::HALF, hipdnn_flatbuffers_sdk::data_objects::DataType::BFLOAT16, &tensorAttributes, &nodes); builder.Finish(graphOffset); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_FALSE(applicable); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsFalseWhenBnOutputIsNotVirtualInFusion) { // Create fusion graph where BN output tensor is non-virtual (should be virtual) flatbuffers::FlatBufferBuilder builder; std::vector<::flatbuffers::Offset> tensorAttributes; const std::vector strides = {1, 3, 14, 14}; const std::vector dims = {1, 3, 14, 14}; const std::vector derivedStrides = {1, 3, 1, 1}; const std::vector derivedDims = {1, 3, 1, 1}; tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 1, "x", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &strides, &dims)); // BN output NOT virtual (should be virtual for fusion) tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 2, "y_bn", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &strides, &dims, false)); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 3, "scale", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &derivedStrides, &derivedDims)); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 4, "bias", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &derivedStrides, &derivedDims)); const hipdnn_flatbuffers_sdk::data_objects::Float32Value epsilonVal(1e-5f); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributes( builder, 5, builder.CreateString("epsilon"), hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, 0, 0, false, hipdnn_flatbuffers_sdk::data_objects::TensorValue::Float32Value, builder.CreateStruct(epsilonVal).Union())); tensorAttributes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateTensorAttributesDirect( builder, 7, "final_out", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, &strides, &dims)); std::vector<::flatbuffers::Offset> nodes; auto bnormAttributes = hipdnn_flatbuffers_sdk::data_objects::CreateBatchnormAttributes(builder, 1, 3, 4, 5, 0, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 2, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "bn", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::BatchnormAttributes, bnormAttributes.Union())); auto actAttr = hipdnn_flatbuffers_sdk::data_objects::CreatePointwiseAttributes( builder, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::RELU_FWD, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, flatbuffers::nullopt, 2, flatbuffers::nullopt, flatbuffers::nullopt, 7); nodes.push_back(hipdnn_flatbuffers_sdk::data_objects::CreateNodeDirect( builder, "act", hipdnn_flatbuffers_sdk::data_objects::DataType::UNSET, hipdnn_flatbuffers_sdk::data_objects::NodeAttributes::PointwiseAttributes, actAttr.Union())); auto graphOffset = hipdnn_flatbuffers_sdk::data_objects::CreateGraphDirect( builder, "test", hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, hipdnn_flatbuffers_sdk::data_objects::DataType::HALF, hipdnn_flatbuffers_sdk::data_objects::DataType::BFLOAT16, &tensorAttributes, &nodes); builder.Finish(graphOffset); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const bool applicable = _planBuilder.isApplicable(*_dummyHandle, graph); EXPECT_FALSE(applicable); } // ============================================================================ // 3D Tests // ============================================================================ TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsTrueFor3dNclSingleNode) { const std::vector dims3D = {2, 3, 14}; const std::vector strides3D = {42, 14, 1}; auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingGraph(strides3D, dims3D); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); EXPECT_TRUE(_planBuilder.isApplicable(*_dummyHandle, graph)); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, IsApplicableReturnsTrueFor3dNlcFusedTwoNodeGraph) { const std::vector dims3D = {2, 3, 14}; const std::vector strides3D = {42, 1, 3}; auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph( true, false, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::RELU_FWD, strides3D, dims3D); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); EXPECT_TRUE(_planBuilder.isApplicable(*_dummyHandle, graph)); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, BuildPlanSetsPlanFor3dNclSingleNode) { const std::vector dims3D = {2, 3, 14}; const std::vector strides3D = {42, 14, 1}; auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingGraph(strides3D, dims3D); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const MockEngineConfig mockEngineConfig; HipdnnMiopenContext ctx; EXPECT_NO_THROW(_planBuilder.buildPlan(*_dummyHandle, graph, mockEngineConfig, ctx)); EXPECT_TRUE(ctx.hasValidPlan()); } TEST_F(TestMiopenBatchnormFwdTrainingActivPlanBuilder, BuildPlanSetsPlanFor3dNlcFusedTwoNodeGraph) { const std::vector dims3D = {2, 3, 14}; const std::vector strides3D = {42, 1, 3}; auto builder = hipdnn_test_sdk::utilities::createValidBatchnormFwdTrainingActivGraph( true, false, hipdnn_flatbuffers_sdk::data_objects::PointwiseMode::RELU_FWD, strides3D, dims3D); const hipdnn_flatbuffers_sdk::flatbuffer_utilities::GraphWrapper graph( builder.GetBufferPointer(), builder.GetSize()); const MockEngineConfig mockEngineConfig; HipdnnMiopenContext ctx; EXPECT_NO_THROW(_planBuilder.buildPlan(*_dummyHandle, graph, mockEngineConfig, ctx)); EXPECT_TRUE(ctx.hasValidPlan()); }