// Copyright © Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include #include #include #include "test_plugins/TestPluginConstants.hpp" using namespace hipdnn_frontend; using namespace hipdnn_frontend::graph; using namespace hipdnn_data_sdk::utilities; namespace { enum class FailurePoint { NONE, // No failure expected CREATE_EXECUTION_PLAN, // Expect failure at create execution plan EXECUTE // Expect failure at execute }; struct IntegrationTestCase { std::string pluginPath; std::string description; std::string graphName; FailurePoint expectedFailure; bool useManualUids; friend std::ostream& operator<<(std::ostream& os, const IntegrationTestCase& tc) { os << "ConvTestCase{" << "plugin_path: " << tc.pluginPath << ", description: " << tc.description << ", graph_name: " << tc.graphName << ", expected_failure: "; switch(tc.expectedFailure) { case FailurePoint::NONE: os << "NONE"; break; case FailurePoint::CREATE_EXECUTION_PLAN: os << "CREATE_EXECUTION_PLAN"; break; case FailurePoint::EXECUTE: os << "EXECUTE"; break; default: os << "UNKNOWN"; break; } os << ", use_manual_uids: " << (tc.useManualUids ? "true" : "false") << "}"; return os; } }; // Test class for frontend integration tests. // This class runs end-to-end tests to ensure the frontend API works as expected. // Notes: // - We are using fake test plugins to simulate different scenarios. // - The tests will validate the graph creation, execution plan building, and execution through the full flow. // - We are using a batchnorm graph since the graph doesn't really matter due to fake plugins. template class IntegrationConvolutionForward : public ::testing::TestWithParam { protected: // Simplified tensor bundle for frontend integration tests struct SimpleConvolutionTensorBundle { SimpleConvolutionTensorBundle(const std::vector& xDims, const std::vector& wDims, const std::vector& yDims) : xTensor(Tensor(xDims)) , wTensor(Tensor(wDims)) , yTensor(Tensor(yDims)) { // Initialize with simple constant values xTensor.fillWithValue(static_cast(1.0)); wTensor.fillWithValue(static_cast(1.0)); yTensor.fillWithValue(static_cast(0.0)); } Tensor xTensor; Tensor wTensor; Tensor yTensor; }; struct ConvolutionTestTensors { std::shared_ptr x; std::shared_ptr w; std::shared_ptr y; }; void SetUp() override { SKIP_IF_NO_DEVICES(); ASSERT_EQ(hipInit(0), hipSuccess); int deviceId = 0; ASSERT_EQ(hipGetDevice(&deviceId), hipSuccess); } void TearDown() override { if(_handle != nullptr) { ASSERT_EQ(hipdnnDestroy(_handle), HIPDNN_STATUS_SUCCESS); } } static hipdnnHandle_t setupEnvironmentWithPlugin(const std::string& pluginPath) { // Set up plugin path - load specific plugin by absolute path const std::array paths = {pluginPath.c_str()}; EXPECT_EQ(hipdnnSetEnginePluginPaths_ext( paths.size(), paths.data(), HIPDNN_PLUGIN_LOADING_ABSOLUTE), HIPDNN_STATUS_SUCCESS); // Create handle hipdnnHandle_t handle = nullptr; EXPECT_EQ(hipdnnCreate(&handle), HIPDNN_STATUS_SUCCESS); return handle; } static std::pair, ConvolutionTestTensors> createConvTestGraphWithUids(const std::string& graphName, const SimpleConvolutionTensorBundle& tensorBundle, const std::vector& prePadding, const std::vector& postPadding, const std::vector& stride, const std::vector& dilation, bool useManualUids) { auto graph = std::make_shared(); graph->set_name(graphName) .set_io_data_type(DataType::FLOAT) .set_intermediate_data_type(DataType::FLOAT) .set_compute_data_type(DataType::FLOAT); int64_t uid = 1; ConvolutionTestTensors tensors; auto xAttr = makeTensorAttributes("X", getDataTypeEnumFromType(), tensorBundle.xTensor); if(useManualUids) { xAttr.set_uid(uid++); } tensors.x = std::make_shared(std::move(xAttr)); auto wAttr = makeTensorAttributes("W", getDataTypeEnumFromType(), tensorBundle.wTensor); if(useManualUids) { wAttr.set_uid(uid++); } tensors.w = std::make_shared(std::move(wAttr)); ConvFpropAttributes convAttrs; convAttrs.set_name("convolution_forward"); convAttrs.set_pre_padding(prePadding); convAttrs.set_post_padding(postPadding); convAttrs.set_stride(stride); convAttrs.set_dilation(dilation); tensors.y = graph->conv_fprop(tensors.x, tensors.w, convAttrs); if(useManualUids) { tensors.y->set_uid(uid++); } tensors.y->set_output(true); return {graph, tensors}; } static std::unordered_map createVariantPack(const ConvolutionTestTensors& tensors, SimpleConvolutionTensorBundle& tensorBundle) { std::unordered_map variantPack; variantPack[tensors.x->get_uid()] = tensorBundle.xTensor.memory().deviceData(); variantPack[tensors.w->get_uid()] = tensorBundle.wTensor.memory().deviceData(); variantPack[tensors.y->get_uid()] = tensorBundle.yTensor.memory().deviceData(); return variantPack; } static void runGraphPipeline(const std::shared_ptr& graph, hipdnnHandle_t handle, const ConvolutionTestTensors& tensors, SimpleConvolutionTensorBundle& tensorBundle, FailurePoint expectedFailure = FailurePoint::NONE) { auto result = graph->validate(); ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; result = graph->build_operation_graph(handle); ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; result = graph->create_execution_plans(); if(expectedFailure == FailurePoint::CREATE_EXECUTION_PLAN) { ASSERT_NE(result.code, ErrorCode::OK) << "create_execution_plans should fail"; return; } ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; result = graph->check_support(); ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; result = graph->build_plans(); ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; int64_t workspaceSize; result = graph->get_workspace_size(workspaceSize); ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; ASSERT_GE(workspaceSize, 0) << result.err_msg; const Workspace workspace(static_cast(workspaceSize)); ASSERT_TRUE(tensors.x->has_uid()); ASSERT_TRUE(tensors.w->has_uid()); ASSERT_TRUE(tensors.y->has_uid()); auto variantPack = createVariantPack(tensors, tensorBundle); result = graph->execute(handle, variantPack, workspace.get()); if(expectedFailure == FailurePoint::EXECUTE) { ASSERT_NE(result.code, ErrorCode::OK) << "Execute should fail"; } else { ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg; } } void runTest() { const auto& testCase = GetParam(); const std::vector xDims = {2, 3, 14, 14}; // n, c, h, w const std::vector wDims = {3, 3, 3, 3}; // k, c, h, w const std::vector yDims = {2, 3, 12, 12}; // n, k, h, w const std::vector prePadding = {0, 0}; const std::vector postPadding = {0, 0}; const std::vector stride = {1, 1}; const std::vector dilation = {1, 1}; // Setup environment with specified plugin _handle = setupEnvironmentWithPlugin(testCase.pluginPath); // Create tensor bundle SimpleConvolutionTensorBundle tensorBundle(xDims, wDims, yDims); // Create graph and tensors using the unified function auto [graph, tensors] = createConvTestGraphWithUids(testCase.graphName, tensorBundle, prePadding, postPadding, stride, dilation, testCase.useManualUids); runGraphPipeline(graph, _handle, tensors, tensorBundle, testCase.expectedFailure); } private: hipdnnHandle_t _handle = nullptr; }; } // namespace class IntegrationConvolutionForwardFp32 : public IntegrationConvolutionForward { }; TEST_P(IntegrationConvolutionForwardFp32, ExecutePluginPipeline) { runTest(); } INSTANTIATE_TEST_SUITE_P( , IntegrationConvolutionForwardFp32, ::testing::Values( IntegrationTestCase{hipdnn_tests::plugin_constants::testGoodPluginPath(), "DefaultPluginWithManualUids", "DefaultPluginBatchnormTest", FailurePoint::NONE, true}, IntegrationTestCase{hipdnn_tests::plugin_constants::testGoodPluginPath(), "DefaultPluginWithAutoUids", "DefaultPluginBatchnormTestAutoUID", FailurePoint::NONE, false}, IntegrationTestCase{hipdnn_tests::plugin_constants::testExecuteFailsPluginPath(), "ExecuteFailsPlugin", "ExecuteFailsPluginBatchnormTest", FailurePoint::EXECUTE, true}, IntegrationTestCase{hipdnn_tests::plugin_constants::testNoApplicableEnginesAPluginPath(), "NoApplicableEnginesPlugin", "NoEnginesPluginBatchnormTest", FailurePoint::CREATE_EXECUTION_PLAN, true}), // Provide a custom name for each test instance [](const ::testing::TestParamInfo& info) { std::string name = info.param.description; std::transform(name.cbegin(), name.cend(), name.begin(), [](char c) { return std::isalnum(static_cast(c)) ? c : '_'; }); return name; });