Files
TheRock/rocm-libraries/projects/hipdnn/samples/autotune/AutotuneSample.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

818 lines
30 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
// Standalone sample demonstrating the hipDNN autotune API end-to-end.
// Follows the pattern of KnobsUsage.cpp: a main() with demonstration
// scenarios in individual functions.
//
// Run with --help for usage information.
// Default mode uses compact tensors and the RUN_UNTIL_STABLE strategy. Use
// --strategy=average to benchmark with FIXED_AVERAGE instead, --iterations=N to
// set the iteration count, and --large for larger tensor dimensions.
#include <algorithm>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <hipdnn_data_sdk/utilities/EngineNames.hpp>
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_data_sdk/utilities/Workspace.hpp>
#include <hipdnn_frontend.hpp>
#include "../utils/Helpers.hpp"
using namespace hipdnn_frontend;
using namespace hipdnn_data_sdk;
// --- Helpers ---
/// Retrieves the engine name for a given engine ID. Returns a hex-formatted
/// fallback string when the engine ID is not in the registered engine map.
static std::string getEngineName(int64_t engineId)
{
try
{
return std::string(utilities::getEngineNameFromId(engineId));
}
catch(const std::out_of_range&)
{
std::ostringstream oss;
oss << "engine_0x" << std::hex << std::uppercase << engineId;
return oss.str();
}
}
// --- ConvGraph helper ---
/// All state needed to run autotune scenarios against a convolution graph.
struct ConvGraphState
{
std::shared_ptr<graph::Graph> graph;
std::shared_ptr<graph::Tensor_attributes> xAttr;
std::shared_ptr<graph::Tensor_attributes> wAttr;
std::shared_ptr<graph::Tensor_attributes> yAttr;
std::unique_ptr<utilities::Tensor<float>> xTensor;
std::unique_ptr<utilities::Tensor<float>> wTensor;
std::unique_ptr<utilities::Tensor<float>> yTensor;
std::unordered_map<int64_t, void*> variantPack;
};
/// Creates a convolution fprop graph, validates it, builds the operation graph,
/// and allocates device tensors. Does NOT call build() (that is the simple path).
/// For autotune, call add_*() + autotune() after this function.
static ConvGraphState buildConvGraph(hipdnnHandle_t handle, bool largeMode)
{
// Tensor dimensions: compact by default for fast completion, larger with --large
// for meaningful timing differentiation.
const int64_t n = largeMode ? 64 : 1;
const int64_t c = largeMode ? 32 : 4;
const int64_t h = largeMode ? 32 : 4;
const int64_t w = largeMode ? 32 : 4;
const int64_t k = largeMode ? 32 : 4;
// Filter channels must match input channels
const int64_t r = 3;
const int64_t s = 3;
const int64_t padH = 1;
const int64_t padW = 1;
const int64_t strideH = 1;
const int64_t strideW = 1;
const int64_t dilH = 1;
const int64_t dilW = 1;
const auto inputType = hipdnn_frontend::DataType::FLOAT;
const auto layout = utilities::TensorLayout::NCHW;
auto graph = std::make_shared<graph::Graph>();
graph->set_io_data_type(inputType).set_compute_data_type(hipdnn_frontend::DataType::FLOAT);
auto xAttr = createTensor({n, c, h, w}, inputType, layout);
auto wAttr = createTensor({k, c, r, s}, inputType, layout);
graph::ConvFpropAttributes convAttributes;
convAttributes.set_name("autotune_conv_fprop");
convAttributes.set_padding({padH, padW});
convAttributes.set_stride({strideH, strideW});
convAttributes.set_dilation({dilH, dilW});
auto yAttr = graph->conv_fprop(xAttr, wAttr, convAttributes);
yAttr->set_output(true);
HIPDNN_FE_CHECK(graph->validate());
HIPDNN_FE_CHECK(graph->build_operation_graph(handle));
// Allocate device tensors
auto xTensor = std::make_unique<utilities::Tensor<float>>(xAttr->get_dim(), layout);
auto wTensor = std::make_unique<utilities::Tensor<float>>(wAttr->get_dim(), layout);
auto yTensor = std::make_unique<utilities::Tensor<float>>(yAttr->get_dim(), layout);
xTensor->fillWithRandomValues(0.0f, 1.0f);
wTensor->fillWithRandomValues(0.0f, 1.0f);
yTensor->fillWithValue(0.0f);
std::unordered_map<int64_t, void*> variantPack;
variantPack[xAttr->get_uid()] = xTensor->memory().deviceData();
variantPack[wAttr->get_uid()] = wTensor->memory().deviceData();
variantPack[yAttr->get_uid()] = yTensor->memory().deviceData();
ConvGraphState state;
state.graph = std::move(graph);
state.xAttr = std::move(xAttr);
state.wAttr = std::move(wAttr);
state.yAttr = std::move(yAttr);
state.xTensor = std::move(xTensor);
state.wTensor = std::move(wTensor);
state.yTensor = std::move(yTensor);
state.variantPack = std::move(variantPack);
return state;
}
// --- Scenario 1: Quick Autotune ---
/// Simplest possible autotune flow:
/// add_all_engines() -> autotune() -> execute()
static void demonstrateStandardAutotune(hipdnnHandle_t handle,
bool largeMode,
AutotuneStrategy strategy,
int iterations)
{
std::cout << "\n=== Scenario 1: Quick Autotune ===\n";
auto state = buildConvGraph(handle, largeMode);
// Discover and add all available engines
HIPDNN_FE_CHECK(state.graph->add_all_engines());
// Allocate workspace for the largest engine
int64_t maxWs = 0;
HIPDNN_FE_CHECK(state.graph->get_estimated_max_workspace_size(maxWs));
const utilities::Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = strategy;
config.warmupIterations = 1;
if(iterations > 0)
{
if(strategy == AutotuneStrategy::RUN_UNTIL_STABLE)
{
config.maxIterations = iterations;
// windowSize must stay <= maxIterations and >= 2
config.windowSize = std::max(2, std::min(config.windowSize, iterations));
}
else // FIXED_AVERAGE
{
config.timedIterations = iterations;
}
}
// Autotune selects the fastest engine and sets it as active
HIPDNN_FE_CHECK(
state.graph->autotune(handle, state.variantPack, workspace.get(), maxWs, config));
// After autotune(), get_workspace_size() returns the active plan's workspace
int64_t ws = 0;
HIPDNN_FE_CHECK(state.graph->get_workspace_size(ws));
const utilities::Workspace execWorkspace(static_cast<size_t>(ws));
// Execute with the autotuned engine
HIPDNN_FE_CHECK(state.graph->execute(handle, state.variantPack, execWorkspace.get()));
// Verify output
state.yTensor->memory().markDeviceModified();
const auto* yHostPtr = state.yTensor->memory().hostData();
std::cout << " Execution OK, output[0..4]: ";
for(int i = 0; i < 5; ++i)
{
std::cout << yHostPtr[i] << " ";
}
std::cout << '\n';
std::cout << " Autotuned successfully.\n";
}
// --- Scenario 2: Exhaustive Autotune with Result Inspection ---
/// Uses EXHAUSTIVE mode and inspects the ranked results.
/// Prints a table showing all engines with timing and status.
static void demonstrateExhaustiveAutotune(hipdnnHandle_t handle,
bool largeMode,
AutotuneStrategy strategy,
int iterations)
{
std::cout << "\n=== Scenario 2: Exhaustive Autotune ===\n";
auto state = buildConvGraph(handle, largeMode);
// Discover and add all available engines
HIPDNN_FE_CHECK(state.graph->add_all_engines());
int64_t maxWs = 0;
HIPDNN_FE_CHECK(state.graph->get_estimated_max_workspace_size(maxWs));
const utilities::Workspace workspace(static_cast<size_t>(maxWs));
// EXHAUSTIVE mode; strategy/iterations applied the same way as Scenario 1
AutotuneConfig config;
config.mode = TuneMode::EXHAUSTIVE;
config.strategy = strategy;
config.warmupIterations = 1;
config.primingFailurePolicy = PrimingFailurePolicy::BENCHMARK_UNPRIMED;
if(iterations > 0)
{
if(strategy == AutotuneStrategy::RUN_UNTIL_STABLE)
{
config.maxIterations = iterations;
config.windowSize = std::max(2, std::min(config.windowSize, iterations));
}
else // FIXED_AVERAGE
{
config.timedIterations = iterations;
}
}
// Use the overload that populates results
std::vector<AutotuneResult> results;
HIPDNN_FE_CHECK(state.graph->autotune(
handle, state.variantPack, workspace.get(), maxWs, config, {}, &results));
// Print ranked results table
std::cout << " " << std::left << std::setw(6) << "Rank" << std::setw(30) << "Engine"
<< std::setw(12) << "Min (ms)" << std::setw(12) << "Avg (ms)" << std::setw(14)
<< "Workspace" << std::setw(10) << "Converged" << std::setw(12) << "Exhaustive"
<< '\n';
std::cout << " " << std::string(96, '-') << '\n';
for(const auto& result : results)
{
const std::string rankStr = result.rank >= 0 ? std::to_string(result.rank) : "FAIL";
const std::string name
= result.engineName.empty() ? getEngineName(result.engineId) : result.engineName;
std::cout << " " << std::left << std::setw(6) << rankStr << std::setw(30) << name;
if(result.succeeded)
{
std::cout << std::setw(12) << std::fixed << std::setprecision(4) << result.minTimeMs
<< std::setw(12) << std::fixed << std::setprecision(4) << result.avgTimeMs
<< std::setw(14) << result.workspaceSize << std::setw(10)
<< (result.converged ? "yes" : "no") << std::setw(12)
<< (result.ranExhaustive ? "yes" : "no");
}
else
{
std::cout << std::setw(12) << "n/a" << std::setw(12) << "n/a" << std::setw(14)
<< result.workspaceSize << std::setw(10) << "n/a" << std::setw(12) << "n/a";
if(!result.errorMessage.empty())
{
std::cout << " [" << result.errorMessage << "]";
}
}
std::cout << '\n';
}
std::cout << " Total engines benchmarked: " << results.size() << '\n';
}
// --- Scenario 3: Filtered Autotune ---
/// Demonstrates engine discovery and workspace-constrained autotuning using
/// pre-filtering by estimated workspace size.
///
/// This scenario shows the recommended pattern for workspace-constrained
/// autotune when you want to avoid compiling engines that are too large:
/// 1. Call get_engine_configs() to inspect EngineConfigInfo entries
/// 2. Filter by estimatedWorkspaceSize before adding to the graph
/// 3. Call add_engine_configs() with only the engines that fit
/// 4. Run autotune() on the pre-filtered set
///
/// This avoids compiling engines whose estimated workspace exceeds the budget,
/// saving time compared to the general overload (which compiles all engines
/// first, then filters by actual compiled workspace size).
///
/// Alternative: general autotune overload with workspace limit parameter
// ---
/// Instead of pre-filtering, you can use the general autotune() overload that
/// takes a workspaceSize parameter. This compiles and benchmarks all candidates,
/// then filters out any plan whose actual (compiled) workspace exceeds the limit:
///
/// graph->add_all_engines();
/// int64_t maxWs = 0;
/// graph->get_estimated_max_workspace_size(maxWs);
/// Workspace workspace(maxWs);
///
/// AutotuneConfig config;
/// config.mode = TuneMode::STANDARD;
///
/// int64_t workspaceBudget = 256 * 1024 * 1024; // 256 MB limit
/// std::vector<AutotuneResult> results;
/// // General overload: pass workspaceBudget as the 4th argument
/// graph->autotune(handle, variantPack, workspace.get(),
/// workspaceBudget, config, {}, &results);
///
/// In the general overload, plans exceeding the workspace limit appear in
/// results with succeeded=false. If the fastest plan is too large, the
/// next-best plan that fits is selected automatically.
static void demonstrateFilteredAutotune(hipdnnHandle_t handle,
bool largeMode,
AutotuneStrategy strategy,
int iterations)
{
std::cout << "\n=== Scenario 3: Filtered Autotune (Workspace Constrained) ===\n";
auto state = buildConvGraph(handle, largeMode);
// Step 1: Discover available engines
std::vector<EngineConfigInfo> configs;
HIPDNN_FE_CHECK(state.graph->get_engine_configs(configs));
std::cout << " Discovered " << configs.size() << " engine(s):\n";
for(const auto& cfg : configs)
{
const std::string name
= cfg.engineName.empty() ? getEngineName(cfg.engineId) : cfg.engineName;
std::cout << " " << name << " (workspace=" << cfg.estimatedWorkspaceSize
<< ", exhaustive=" << (cfg.supportsExhaustive ? "yes" : "no")
<< ", knobs=" << cfg.knobs.size() << ")\n";
}
// Step 2: Filter engines by workspace size
// Use a generous limit: allow any engine with workspace <= 256 MB
const int64_t workspaceLimit = int64_t{256} * 1024 * 1024;
std::vector<EngineConfigInfo> filteredConfigs;
for(const auto& cfg : configs)
{
if(cfg.estimatedWorkspaceSize <= workspaceLimit)
{
filteredConfigs.push_back(cfg);
}
}
std::cout << " After filtering (workspace <= " << workspaceLimit
<< " bytes): " << filteredConfigs.size() << " engine(s)\n";
if(filteredConfigs.empty())
{
std::cout << " No engines within workspace limit, skipping autotune.\n";
return;
}
// Step 3: Add only filtered engines
HIPDNN_FE_CHECK(state.graph->add_engine_configs(filteredConfigs));
// Allocate workspace up to the limit
int64_t maxWs = 0;
HIPDNN_FE_CHECK(state.graph->get_estimated_max_workspace_size(maxWs));
const int64_t allocatedWs = std::min(maxWs, workspaceLimit);
const utilities::Workspace workspace(static_cast<size_t>(allocatedWs));
// Step 4: Autotune with pre-filtered engines
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = strategy;
config.warmupIterations = 1;
if(iterations > 0)
{
if(strategy == AutotuneStrategy::RUN_UNTIL_STABLE)
{
config.maxIterations = iterations;
config.windowSize = std::max(2, std::min(config.windowSize, iterations));
}
else // FIXED_AVERAGE
{
config.timedIterations = iterations;
}
}
HIPDNN_FE_CHECK(
state.graph->autotune(handle, state.variantPack, workspace.get(), allocatedWs, config));
std::cout << " Autotuned with workspace constraint successfully.\n";
}
// --- Scenario 4: Save Results to Config File ---
/// Autotunes and saves results to a JSON config file that can be reused via
/// HIPDNN_HEUR_CONFIG_PATH environment variable.
static void demonstrateSaveToConfigFile(hipdnnHandle_t handle,
bool largeMode,
AutotuneStrategy strategy,
int iterations)
{
std::cout << "\n=== Scenario 4: Save Results to Config File ===\n";
const std::filesystem::path configFile = "sample_autotune_results.json";
auto state = buildConvGraph(handle, largeMode);
// Discover and add all available engines
HIPDNN_FE_CHECK(state.graph->add_all_engines());
int64_t maxWs = 0;
HIPDNN_FE_CHECK(state.graph->get_estimated_max_workspace_size(maxWs));
const utilities::Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = strategy;
config.warmupIterations = 1;
if(iterations > 0)
{
if(strategy == AutotuneStrategy::RUN_UNTIL_STABLE)
{
config.maxIterations = iterations;
config.windowSize = std::max(2, std::min(config.windowSize, iterations));
}
else // FIXED_AVERAGE
{
config.timedIterations = iterations;
}
}
// Write results to a config file
const AutotuneStorageConfig storageConfig{configFile, false};
HIPDNN_FE_CHECK(state.graph->autotune(
handle, state.variantPack, workspace.get(), maxWs, config, storageConfig));
std::cout << " Results saved to " << configFile << '\n';
std::cout << " To reuse: export HIPDNN_HEUR_CONFIG_PATH=" << configFile << '\n';
// Clean up the demo file
std::error_code removeEc;
const bool removed = std::filesystem::remove(configFile, removeEc);
if(removed && removeEc.value() == 0)
{
std::cout << " (Demo file cleaned up)\n";
}
}
// --- Scenario 5: Compiled-Plan Autotune ---
/// Demonstrates the compiled-plan autotune path (cuDNN-compatible workflow):
/// create_execution_plans() -> build_plans(ALL) -> autotune() -> execute()
static void demonstrateCompiledPlanAutotune(hipdnnHandle_t handle,
bool largeMode,
AutotuneStrategy strategy,
int iterations)
{
std::cout << "\n=== Scenario 5: Compiled-Plan Autotune ===\n";
auto state = buildConvGraph(handle, largeMode);
// Create execution plans using fallback heuristic mode
HIPDNN_FE_CHECK(state.graph->create_execution_plans({HeuristicMode::FALLBACK}));
// Build all plans so they are available for autotuning
HIPDNN_FE_CHECK(state.graph->build_plans(BuildPlanPolicy::ALL));
// Allocate workspace large enough for any candidate plan
const int64_t maxWs = state.graph->get_autotune_workspace_size();
const utilities::Workspace workspace(static_cast<size_t>(maxWs));
AutotuneConfig config;
config.mode = TuneMode::STANDARD;
config.strategy = strategy;
config.warmupIterations = 1;
if(iterations > 0)
{
if(strategy == AutotuneStrategy::RUN_UNTIL_STABLE)
{
config.maxIterations = iterations;
config.windowSize = std::max(2, std::min(config.windowSize, iterations));
}
else // FIXED_AVERAGE
{
config.timedIterations = iterations;
}
}
// Autotune across all compiled plans
std::vector<AutotuneResult> results;
HIPDNN_FE_CHECK(
state.graph->autotune(handle, state.variantPack, workspace.get(), config, {}, &results));
// Print results table (ranked by autotune performance)
std::cout << " " << std::left << std::setw(6) << "Rank" << std::setw(30) << "Plan Name"
<< std::setw(14) << "Workspace" << std::setw(12) << "Time (ms)" << '\n';
std::cout << " " << std::string(62, '-') << '\n';
for(size_t i = 0; i < results.size(); ++i)
{
const auto& result = results[i];
const std::string name
= result.engineName.empty() ? getEngineName(result.engineId) : result.engineName;
std::cout << " " << std::left << std::setw(6) << i << std::setw(30) << name
<< std::setw(14) << result.workspaceSize;
if(result.succeeded)
{
std::cout << std::setw(12) << std::fixed << std::setprecision(4) << result.minTimeMs;
}
else
{
std::cout << std::setw(12) << "FAIL";
}
std::cout << '\n';
}
// Execute with the autotuned winner
int64_t ws = 0;
HIPDNN_FE_CHECK(state.graph->get_workspace_size(ws));
const utilities::Workspace execWorkspace(static_cast<size_t>(ws));
HIPDNN_FE_CHECK(state.graph->execute(handle, state.variantPack, execWorkspace.get()));
std::string winnerName;
HIPDNN_FE_CHECK(state.graph->get_plan_name(winnerName));
std::cout << " Winner: " << winnerName << '\n';
std::cout << " Compiled-plan autotune completed successfully.\n";
}
// --- Scenario 6: Manual Benchmark Loop ---
/// Demonstrates the cuDNN-compatible manual benchmark loop:
/// create_execution_plans() -> build_plans(ALL) -> plan-indexed iteration
/// with HIP event timing and workspace filtering.
static void demonstrateManualBenchmarkLoop(hipdnnHandle_t handle, bool largeMode)
{
std::cout << "\n=== Scenario 6: Manual Benchmark Loop ===\n";
auto state = buildConvGraph(handle, largeMode);
// Create execution plans and build all candidates
HIPDNN_FE_CHECK(state.graph->create_execution_plans({HeuristicMode::FALLBACK}));
HIPDNN_FE_CHECK(state.graph->build_plans(BuildPlanPolicy::ALL));
const int64_t planCount = state.graph->get_execution_plan_count();
std::cout << " Found " << planCount << " compiled plan(s)\n";
// Print plan info table before benchmarking
std::cout << " " << std::left << std::setw(8) << "Index" << std::setw(30) << "Plan Name"
<< std::setw(14) << "Workspace" << '\n';
std::cout << " " << std::string(52, '-') << '\n';
for(int64_t i = 0; i < planCount; ++i)
{
std::string name;
HIPDNN_FE_CHECK(state.graph->get_plan_name_at_index(i, name));
const int64_t planWs = state.graph->get_workspace_size_plan_at_index(i);
std::cout << " " << std::left << std::setw(8) << i << std::setw(30) << name
<< std::setw(14) << planWs << '\n';
}
// Workspace budget: 256 MB
const int64_t workspaceBudget = int64_t{256} * 1024 * 1024;
// Allocate workspace large enough for any candidate plan
const int64_t maxWs = state.graph->get_autotune_workspace_size();
const utilities::Workspace workspace(static_cast<size_t>(maxWs));
// Manual benchmark loop with HIP event timing
hipEvent_t startEvent;
hipEvent_t stopEvent;
HIP_CHECK(hipEventCreate(&startEvent));
HIP_CHECK(hipEventCreate(&stopEvent));
float bestTimeMs = std::numeric_limits<float>::max();
int64_t bestIndex = -1;
std::cout << "\n Benchmarking plans:\n";
for(int64_t i = 0; i < planCount; ++i)
{
const int64_t planWs = state.graph->get_workspace_size_plan_at_index(i);
if(planWs > workspaceBudget)
{
std::string name;
HIPDNN_FE_CHECK(state.graph->get_plan_name_at_index(i, name));
std::cout << " [" << i << "] " << name << " -- skipped (workspace " << planWs
<< " exceeds budget)\n";
continue;
}
HIP_CHECK(hipEventRecord(startEvent));
auto result
= state.graph->execute_plan_at_index(handle, state.variantPack, workspace.get(), i);
HIP_CHECK(hipEventRecord(stopEvent));
HIP_CHECK(hipEventSynchronize(stopEvent));
if(!result.is_good())
{
std::string name;
HIPDNN_FE_CHECK(state.graph->get_plan_name_at_index(i, name));
std::cout << " [" << i << "] " << name << " -- failed (" << result.get_message()
<< ")\n";
continue;
}
float elapsedMs = 0.0f;
HIP_CHECK(hipEventElapsedTime(&elapsedMs, startEvent, stopEvent));
std::string name;
HIPDNN_FE_CHECK(state.graph->get_plan_name_at_index(i, name));
std::cout << " [" << i << "] " << name << " -- " << std::fixed << std::setprecision(4)
<< elapsedMs << " ms\n";
if(elapsedMs < bestTimeMs)
{
bestTimeMs = elapsedMs;
bestIndex = i;
}
}
HIP_CHECK(hipEventDestroy(startEvent));
HIP_CHECK(hipEventDestroy(stopEvent));
if(bestIndex < 0)
{
std::cout << " No plan succeeded during manual benchmark loop.\n";
return;
}
// Activate the best plan
HIPDNN_FE_CHECK(state.graph->build_plan_at_index(bestIndex));
int64_t ws = 0;
HIPDNN_FE_CHECK(state.graph->get_workspace_size(ws));
const utilities::Workspace execWorkspace(static_cast<size_t>(ws));
HIPDNN_FE_CHECK(state.graph->execute(handle, state.variantPack, execWorkspace.get()));
std::string winnerName;
HIPDNN_FE_CHECK(state.graph->get_plan_name(winnerName));
std::cout << " Winner: " << winnerName << " (" << std::fixed << std::setprecision(4)
<< bestTimeMs << " ms)\n";
std::cout << " Manual benchmark loop completed successfully.\n";
}
// --- Argument Parsing ---
struct AutotuneSampleConfig
{
int scenario = 0; // 0 = run all, 1-6 = specific scenario
bool largeMode = false;
AutotuneStrategy strategy = AutotuneStrategy::RUN_UNTIL_STABLE; // --strategy
int iterations = 0; // -- iterations; 0 = strategy defaults, >0 = override
};
static AutotuneSampleConfig parseArgs(int argc, char** argv)
{
AutotuneSampleConfig cfg;
for(int i = 1; i < argc; ++i)
{
const std::string arg(argv[i]);
if(arg == "--help" || arg == "-h")
{
std::cout
<< "Usage: " << argv[0] << " [OPTIONS]\n"
<< " --scenario=N Run specific scenario (1-6, default=all)\n"
<< " --large Use larger tensor dimensions\n"
<< " --strategy=S Benchmarking strategy: stable (RUN_UNTIL_STABLE,\n"
<< " default) or average (FIXED_AVERAGE)\n"
<< " --iterations=N Iteration count: max iterations for stable, timed\n"
<< " iterations for average (default: strategy default)\n"
<< " --verify-cpu Accepted but ignored (compatibility with test harness)\n"
<< " --help, -h Show this help\n"
<< "\n"
<< "Scenarios:\n"
<< " Plan-spec path (hipDNN-native autotune):\n"
<< " 1 Quick autotune add_all_engines -> autotune -> execute\n"
<< " 2 Exhaustive autotune EXHAUSTIVE mode with ranked result table\n"
<< " 3 Filtered autotune get_engine_configs -> workspace filter -> "
"add_engine_configs\n"
<< " 4 Save to config file autotune with AutotuneStorageConfig for "
"persistence\n"
<< "\n"
<< " Compiled-plan path (cuDNN-compatible):\n"
<< " 5 Compiled-plan autotune create_execution_plans -> build_plans(ALL) -> "
"autotune\n"
<< " 6 Manual benchmark loop Plan-indexed iteration with timing and "
"workspace filtering\n";
exit(EXIT_SUCCESS);
}
if(arg.rfind("--scenario=", 0) == 0)
{
cfg.scenario = std::stoi(arg.substr(11));
if(cfg.scenario < 1 || cfg.scenario > 6)
{
std::cerr << "Invalid scenario: " << cfg.scenario << " (must be 1-6)\n";
exit(EXIT_FAILURE);
}
}
else if(arg == "--large")
{
cfg.largeMode = true;
}
else if(arg.rfind("--strategy=", 0) == 0)
{
const std::string value = arg.substr(11);
if(value == "stable")
{
cfg.strategy = AutotuneStrategy::RUN_UNTIL_STABLE;
}
else if(value == "average")
{
cfg.strategy = AutotuneStrategy::FIXED_AVERAGE;
}
else
{
std::cerr << "Invalid --strategy: " << value << " (use stable or average)\n";
exit(EXIT_FAILURE);
}
}
else if(arg.rfind("--iterations=", 0) == 0)
{
cfg.iterations = std::stoi(arg.substr(13));
if(cfg.iterations < 1)
{
std::cerr << "--iterations count must be >= 1\n";
exit(EXIT_FAILURE);
}
}
else if(arg == "--verify-cpu" || arg == "-vc")
{
// Accepted but ignored: autotune has no CPU validation path.
// This flag is passed by add_hipdnn_sample_test().
}
else
{
std::cerr << "Unknown argument: " << arg << " (use --help)\n";
exit(EXIT_FAILURE);
}
}
return cfg;
}
// --- Main ---
int main(int argc, char* argv[])
{
try
{
const auto config = parseArgs(argc, argv);
std::cout << "hipDNN Autotune Sample"
<< (config.largeMode ? " (large mode)" : " (fast mode)") << '\n';
initializeFrontendLogging();
RETURN_SUCCESS_IF_NO_DEVICE();
hipdnnHandle_t handle = nullptr;
HIPDNN_CHECK(hipdnnCreate(&handle));
if(config.scenario == 0 || config.scenario == 1)
{
demonstrateStandardAutotune(
handle, config.largeMode, config.strategy, config.iterations);
}
if(config.scenario == 0 || config.scenario == 2)
{
demonstrateExhaustiveAutotune(
handle, config.largeMode, config.strategy, config.iterations);
}
if(config.scenario == 0 || config.scenario == 3)
{
demonstrateFilteredAutotune(
handle, config.largeMode, config.strategy, config.iterations);
}
if(config.scenario == 0 || config.scenario == 4)
{
demonstrateSaveToConfigFile(
handle, config.largeMode, config.strategy, config.iterations);
}
if(config.scenario == 0 || config.scenario == 5)
{
demonstrateCompiledPlanAutotune(
handle, config.largeMode, config.strategy, config.iterations);
}
if(config.scenario == 0 || config.scenario == 6)
{
demonstrateManualBenchmarkLoop(handle, config.largeMode);
}
HIPDNN_CHECK(hipdnnDestroy(handle));
std::cout << "\nDone.\n";
return 0;
}
catch(const std::exception& e)
{
std::cerr << "Error: " << e.what() << '\n';
return 1;
}
}