Files
TheRock/rocm-libraries/projects/hipdnn/tests/frontend/IntegrationAutotuneCompiledPlanPath.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

433 lines
16 KiB
C++

// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
// Integration tests for the compiled-plan autotune path:
// create_execution_plans() -> build_plans(ALL) -> autotune() -> execute().
// Also tests the manual benchmark loop using plan-indexed access APIs.
#include <gtest/gtest.h>
#include <hip/hip_runtime.h>
#include <memory>
#include <unordered_map>
#include <vector>
#include <hipdnn_data_sdk/utilities/Workspace.hpp>
#include <hipdnn_frontend.hpp>
#include "AutotuneIntegrationFixture.hpp"
using namespace hipdnn_frontend;
using namespace hipdnn_frontend::graph;
using namespace hipdnn_data_sdk::utilities;
namespace
{
using IntegrationAutotuneCompiledPlanPath = hipdnn_tests::AutotuneIntegrationFixture;
// Test: compiled-plan path autotune end-to-end workflow
TEST_F(IntegrationAutotuneCompiledPlanPath, CompiledPlanAutotuneEndToEnd)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
ASSERT_GE(maxWs, 0);
const Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = AutotuneStrategy::FIXED_AVERAGE;
config.timedIterations = 1;
config.warmupIterations = 1;
std::vector<AutotuneResult> results;
result = bundle.graph->autotune(
_handle, bundle.variantPack, workspace.get(), config, {}, &results);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
assertAnySucceeded(results, "No engine succeeded during compiled-plan autotune");
buildWorkspaceAndExecute(bundle);
}
// Test: EXHAUSTIVE mode is rejected on the compiled-plan path
TEST_F(IntegrationAutotuneCompiledPlanPath, CompiledPlanAutotuneExhaustiveBlocked)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::EXHAUSTIVE;
config.strategy = AutotuneStrategy::FIXED_AVERAGE;
config.timedIterations = 1;
config.warmupIterations = 1;
std::vector<AutotuneResult> results;
result = bundle.graph->autotune(
_handle, bundle.variantPack, workspace.get(), config, {}, &results);
EXPECT_EQ(result.code, ErrorCode::INVALID_VALUE) << result.err_msg;
EXPECT_NE(result.err_msg.find("EXHAUSTIVE mode is not supported"), std::string::npos)
<< result.err_msg;
}
// Test: manual benchmark loop using plan-indexed access APIs
TEST_F(IntegrationAutotuneCompiledPlanPath, ManualLoopEndToEnd)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t planCount = bundle.graph->get_execution_plan_count();
ASSERT_GT(planCount, 0);
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
int64_t bestIndex = -1;
for(int64_t i = 0; i < planCount; ++i)
{
auto execResult
= bundle.graph->execute_plan_at_index(_handle, bundle.variantPack, workspace.get(), i);
if(execResult.is_good())
{
bestIndex = i;
break;
}
}
ASSERT_GE(bestIndex, 0) << "No plan succeeded during manual loop";
result = bundle.graph->build_plan_at_index(bestIndex);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
buildWorkspaceAndExecute(bundle);
}
// Test: multi-engine plugin provides multiple plans and autotune benchmarks them
TEST_F(IntegrationAutotuneCompiledPlanPath, CompiledPlanAutotuneMultipleEngines)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
// With multi-engine plugin, should have multiple plans
const int64_t planCount = bundle.graph->get_execution_plan_count();
ASSERT_GE(planCount, 2) << "Multi-engine plugin should provide >= 2 plans";
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = AutotuneStrategy::FIXED_AVERAGE;
config.timedIterations = 1;
config.warmupIterations = 1;
std::vector<AutotuneResult> results;
result = bundle.graph->autotune(
_handle, bundle.variantPack, workspace.get(), config, {}, &results);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
// Multiple engines should produce multiple results
ASSERT_GE(results.size(), 2u) << "Autotune should benchmark >= 2 engines";
assertAnySucceeded(results, "At least one engine should succeed");
buildWorkspaceAndExecute(bundle);
}
// Test: build_plans(ALL) compiles multiple engines and they are executable
TEST_F(IntegrationAutotuneCompiledPlanPath, BuildPlansAllCompilesMultipleEngines)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t planCount = bundle.graph->get_execution_plan_count();
ASSERT_GE(planCount, 2);
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
// Verify multiple plans are actually executable
int successCount = 0;
for(int64_t i = 0; i < planCount; ++i)
{
auto execResult
= bundle.graph->execute_plan_at_index(_handle, bundle.variantPack, workspace.get(), i);
if(execResult.is_good())
{
++successCount;
}
}
ASSERT_GE(successCount, 2) << "build_plans(ALL) should compile multiple executable plans";
}
// Test: build_plans(ALL) compiles more plans than HEURISTICS_CHOICE
TEST_F(IntegrationAutotuneCompiledPlanPath, BuildPlansAllVsHeuristicsChoice)
{
// Graph A: build_plans(HEURISTICS_CHOICE) — only compiles the active plan
ConvGraphBundle bundleA;
createBuiltConvGraph("compiled_plan_path_test_conv", bundleA);
auto result = bundleA.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundleA.graph->build_plans(BuildPlanPolicy::HEURISTICS_CHOICE);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t planCountA = bundleA.graph->get_execution_plan_count();
ASSERT_GE(planCountA, 2) << "Multi-engine plugin should provide >= 2 plans";
const int64_t maxWsA = bundleA.graph->get_autotune_workspace_size();
const Workspace workspaceA(static_cast<size_t>(maxWsA));
int successCountA = 0;
for(int64_t i = 0; i < planCountA; ++i)
{
auto execResult = bundleA.graph->execute_plan_at_index(
_handle, bundleA.variantPack, workspaceA.get(), i);
if(execResult.is_good())
{
++successCountA;
}
}
// Graph B: build_plans(ALL) — compiles all plans
ConvGraphBundle bundleB;
createBuiltConvGraph("compiled_plan_path_test_conv", bundleB);
result = bundleB.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundleB.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t planCountB = bundleB.graph->get_execution_plan_count();
const int64_t maxWsB = bundleB.graph->get_autotune_workspace_size();
const Workspace workspaceB(static_cast<size_t>(maxWsB));
int successCountB = 0;
for(int64_t i = 0; i < planCountB; ++i)
{
auto execResult = bundleB.graph->execute_plan_at_index(
_handle, bundleB.variantPack, workspaceB.get(), i);
if(execResult.is_good())
{
++successCountB;
}
}
// ALL should compile more plans than HEURISTICS_CHOICE
ASSERT_GT(successCountB, successCountA)
<< "build_plans(ALL) should compile more plans than HEURISTICS_CHOICE";
}
// Test: manual benchmark loop iterates over multiple engines
TEST_F(IntegrationAutotuneCompiledPlanPath, ManualLoopMultipleEngines)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const int64_t planCount = bundle.graph->get_execution_plan_count();
ASSERT_GE(planCount, 2) << "Multi-engine plugin should provide >= 2 plans";
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
// Iterate all plans, track all successes
std::vector<int64_t> successIndices;
for(int64_t i = 0; i < planCount; ++i)
{
auto execResult
= bundle.graph->execute_plan_at_index(_handle, bundle.variantPack, workspace.get(), i);
if(execResult.is_good())
{
successIndices.push_back(i);
}
}
ASSERT_GE(successIndices.size(), 2u) << "At least 2 plans should succeed during manual loop";
// Select a non-zero index if possible
const int64_t selectedIndex = successIndices.back();
result = bundle.graph->build_plan_at_index(selectedIndex);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
buildWorkspaceAndExecute(bundle);
}
// Test: compiled-plan path autotune with engine ID filter selects specific engine
TEST_F(IntegrationAutotuneCompiledPlanPath, CompiledPlanAutotuneWithEngineIdFilter)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
auto result = bundle.graph->create_execution_plans({HeuristicMode::FALLBACK});
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
result = bundle.graph->build_plans(BuildPlanPolicy::ALL);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
// Verify we have multiple plans before filtering
ASSERT_GE(bundle.graph->get_execution_plan_count(), 2);
const int64_t maxWs = bundle.graph->get_autotune_workspace_size();
const Workspace workspace(static_cast<size_t>(maxWs));
// Filter to only engine B
const int64_t engineBId = hipdnn_tests::plugin_constants::engineId<AutotunePluginEngineB>();
// Engine A exposes the global.benchmarking knob; engine B does not. The
// compiled-plan path computes supportsExhaustive per plan from the engine's
// knobs, so it must be reported on both the benchmarked winner (B) and the
// filtered-out candidates (A) alike.
const int64_t engineAId = hipdnn_tests::plugin_constants::engineId<AutotunePlugin>();
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = AutotuneStrategy::FIXED_AVERAGE;
config.timedIterations = 1;
config.warmupIterations = 1;
config.engineIdFilter = {engineBId};
std::vector<AutotuneResult> results;
result = bundle.graph->autotune(
_handle, bundle.variantPack, workspace.get(), config, {}, &results);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
// Every candidate plan produces a result. The filtered-in engine (B) is the
// only one benchmarked and ranked; the excluded plans surface as filtered
// failed results carrying the engineIdFilter reason.
int benchmarkedEngineB = 0;
int excludedByFilter = 0;
bool sawFilteredEngineA = false;
for(const auto& r : results)
{
// Priming never runs on the compiled-plan path (EXHAUSTIVE is rejected),
// so every result reports ranExhaustive=false with no priming reason.
EXPECT_FALSE(r.ranExhaustive);
EXPECT_TRUE(r.exhaustiveNotRunReason.empty()) << r.exhaustiveNotRunReason;
if(r.engineId == engineBId && r.succeeded)
{
++benchmarkedEngineB;
// Engine B exposes no global.benchmarking knob.
EXPECT_FALSE(r.supportsExhaustive)
<< "Engine B has no benchmarking knob, so supportsExhaustive is false";
}
else
{
++excludedByFilter;
EXPECT_FALSE(r.succeeded);
EXPECT_NE(r.engineId, engineBId) << "Engine B should not be filtered out";
EXPECT_NE(r.errorMessage.find("engineIdFilter"), std::string::npos) << r.errorMessage;
if(r.engineId == engineAId)
{
sawFilteredEngineA = true;
// A filtered-out engine still reports its real benchmarking
// capability; engine A exposes global.benchmarking.
EXPECT_TRUE(r.supportsExhaustive)
<< "Filtered-out engine A still reports supportsExhaustive=true";
}
}
}
EXPECT_EQ(benchmarkedEngineB, 1) << "Filter should benchmark exactly 1 engine (B)";
EXPECT_GT(excludedByFilter, 0) << "Excluded plans should still surface as filtered results";
EXPECT_TRUE(sawFilteredEngineA)
<< "Engine A should appear as a filtered result carrying its capability";
buildWorkspaceAndExecute(bundle);
}
// Test: plan-spec path autotune with engine ID filter selects specific engine
TEST_F(IntegrationAutotuneCompiledPlanPath, PlanSpecAutotuneWithEngineIdFilter)
{
ConvGraphBundle bundle;
createBuiltConvGraph("compiled_plan_path_test_conv", bundle);
// Use plan-spec path (add_all_engines) instead of compiled-plan path
auto result = bundle.graph->add_all_engines();
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
int64_t maxWs = 0;
result = bundle.graph->get_estimated_max_workspace_size(maxWs);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
const Workspace workspace(static_cast<size_t>(maxWs));
// Filter to only engine A (the original autotune engine)
const int64_t engineAId = hipdnn_tests::plugin_constants::engineId<AutotunePlugin>();
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = AutotuneStrategy::FIXED_AVERAGE;
config.timedIterations = 1;
config.warmupIterations = 1;
config.engineIdFilter = {engineAId};
std::vector<AutotuneResult> results;
result = bundle.graph->autotune(
_handle, bundle.variantPack, workspace.get(), maxWs, config, {}, &results);
ASSERT_EQ(result.code, ErrorCode::OK) << result.err_msg;
ASSERT_FALSE(results.empty()) << "No results from engine A found";
// Only engine A's plan specs are benchmarked (succeeded); any plan spec from
// another engine is excluded by the filter and surfaces as a filtered failed
// result carrying the engineIdFilter reason.
bool sawSucceededEngineA = false;
for(const auto& r : results)
{
if(r.engineId == engineAId)
{
if(r.succeeded)
{
sawSucceededEngineA = true;
}
}
else
{
EXPECT_FALSE(r.succeeded) << "Filtered-out engine should not succeed";
EXPECT_NE(r.errorMessage.find("engineIdFilter"), std::string::npos) << r.errorMessage;
}
}
EXPECT_TRUE(sawSucceededEngineA) << "Engine A should be benchmarked and succeed";
buildWorkspaceAndExecute(bundle);
}
} // namespace