Files
TheRock/rocm-libraries/projects/hipdnn/frontend/include/hipdnn_frontend/autotune/AutotuneBenchmark.hpp
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

424 lines
19 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#pragma once
#include <hipdnn_frontend/Error.hpp>
#include <hipdnn_frontend/Logging.hpp>
#include <hipdnn_frontend/Utilities.hpp>
#include <hipdnn_frontend/autotune/AutotuneTypes.hpp>
#include <hipdnn_frontend/autotune/KnobConstants.hpp>
#include <hipdnn_frontend/detail/GraphExecution.hpp>
#include <hipdnn_frontend/detail/ScopedHipdnnBackendDescriptor.hpp>
#include <hipdnn_frontend/knob/KnobSetting.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace hipdnn_frontend::autotune::detail
{
// Run one timed iteration using a fresh profiling control descriptor.
// Creates descriptor, records START -> execute -> STOP -> finalize ->
// ELAPSED_MS. A new descriptor is created each call because
// ProfilingControlDescriptor does not support reset - setAttribute throws
// after finalize.
inline Error
benchmarkOnce(hipdnnHandle_t handle,
::hipdnn_frontend::detail::ScopedHipdnnBackendDescriptor& execPlan,
::hipdnn_frontend::detail::ScopedHipdnnBackendDescriptor& variantPackDesc,
float& elapsedMs)
{
elapsedMs = 0.0f;
// Create a fresh profiling descriptor for this iteration
// NOLINTNEXTLINE(misc-const-correctness)
::hipdnn_frontend::detail::ScopedHipdnnBackendDescriptor profilingDesc(
HIPDNN_BACKEND_PROFILING_CONTROL_EXT);
if(!profilingDesc.valid())
{
return {ErrorCode::HIPDNN_BACKEND_ERROR, "Failed to create profiling control descriptor"};
}
// Set handle (creates HIP events)
HIPDNN_RETURN_ON_BACKEND_FAILURE(
::hipdnn_frontend::detail::hipdnnBackend()->backendSetAttribute(
profilingDesc.get(),
HIPDNN_ATTR_PROFILING_HANDLE_EXT,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle)),
"Failed to set handle on profiling descriptor");
// Record start event
bool startVal = true;
HIPDNN_RETURN_ON_BACKEND_FAILURE(
::hipdnn_frontend::detail::hipdnnBackend()->backendSetAttribute(
profilingDesc.get(),
HIPDNN_ATTR_PROFILING_START_EXT,
HIPDNN_TYPE_BOOLEAN,
1,
&startVal),
"Failed to set profiling start");
// Execute
HIPDNN_CHECK_ERROR(
::hipdnn_frontend::detail::executeWithPlan(handle, execPlan, variantPackDesc));
// Record stop event
bool stopVal = true;
HIPDNN_RETURN_ON_BACKEND_FAILURE(
::hipdnn_frontend::detail::hipdnnBackend()->backendSetAttribute(
profilingDesc.get(), HIPDNN_ATTR_PROFILING_STOP_EXT, HIPDNN_TYPE_BOOLEAN, 1, &stopVal),
"Failed to set profiling stop");
// Finalize synchronizes events and computes elapsed time
HIPDNN_RETURN_ON_BACKEND_FAILURE(
::hipdnn_frontend::detail::hipdnnBackend()->backendFinalize(profilingDesc.get()),
"Failed to finalize profiling descriptor");
// Read elapsed time
HIPDNN_RETURN_ON_BACKEND_FAILURE(
::hipdnn_frontend::detail::hipdnnBackend()->backendGetAttribute(
profilingDesc.get(),
HIPDNN_ATTR_PROFILING_ELAPSED_MS_EXT,
HIPDNN_TYPE_FLOAT,
1,
nullptr,
&elapsedMs),
"Failed to get profiling elapsed ms");
return {ErrorCode::OK, ""};
}
// Initializes an AutotuneResult with the sub-set identity and config fields known
// before a candidate is benchmarked. The benchmark loop fills the timing, succeeded,
// rank, and compiledPlanIndex fields during/after timing.
inline AutotuneResult makeBenchmarkResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
int64_t compiledWorkspaceSize,
const AutotuneConfig& config)
{
AutotuneResult result;
result.engineId = engineId;
result.knobSettings = knobSettings;
result.estimatedWorkspaceSize = estimatedWorkspaceSize;
result.workspaceSize = compiledWorkspaceSize;
result.engineName = ::hipdnn_frontend::detail::resolveEngineName(engineId);
result.modeUsed = config.mode;
result.strategyUsed = config.strategy;
return result;
}
// Shared factory for every non-benchmarked candidate (skipped / barred /
// finalize-failed / compile-failed / filtered). They differ only in the two
// workspace sizes and the error message; everything else is the common
// non-benchmarked values (succeeded==false, rank==-1, compiledPlanIndex==-1).
// A workspace size of -1 means "not applicable / never compiled".
inline AutotuneResult makeNonBenchmarkedResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
int64_t compiledWorkspaceSize,
const AutotuneConfig& config,
const std::string& errorMessage,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
AutotuneResult result;
result.engineId = engineId;
result.knobSettings = knobSettings;
result.estimatedWorkspaceSize = estimatedWorkspaceSize;
result.workspaceSize = compiledWorkspaceSize;
result.succeeded = false;
result.errorMessage = errorMessage;
result.engineName = ::hipdnn_frontend::detail::resolveEngineName(engineId);
result.modeUsed = config.mode;
result.supportsExhaustive = supportsExhaustive;
result.ranExhaustive = ranExhaustive;
result.exhaustiveNotRunReason = exhaustiveNotRunReason;
result.strategyUsed = config.strategy;
result.rank = -1;
result.compiledPlanIndex = -1;
return result;
}
// Non-benchmarked result for a plan excluded by the runtime workspace ceiling. The
// reported workspace size is the actual compiled workspace; estimatedWorkspaceSize
// carries the pre-compile workspace size estimate.
inline AutotuneResult makeSkippedResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
int64_t compiledWorkspaceSize,
const AutotuneConfig& config,
int64_t maxWorkspaceSize,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
return makeNonBenchmarkedResult(engineId,
knobSettings,
estimatedWorkspaceSize,
compiledWorkspaceSize,
config,
"Workspace size " + std::to_string(compiledWorkspaceSize)
+ " exceeds limit " + std::to_string(maxWorkspaceSize),
supportsExhaustive,
ranExhaustive,
exhaustiveNotRunReason);
}
// Non-benchmarked result for a plan barred by a persistent user filter (deselect_engines()
// engine ID or deselect_workspace_greater_than()). compiledWorkspaceSize is -1
// when the plan was never compiled.
inline AutotuneResult makeBarredResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
int64_t compiledWorkspaceSize,
const AutotuneConfig& config,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
return makeNonBenchmarkedResult(engineId,
knobSettings,
estimatedWorkspaceSize,
compiledWorkspaceSize,
config,
"Plan barred (engine ID or workspace deselect filter).",
supportsExhaustive,
ranExhaustive,
exhaustiveNotRunReason);
}
// Failed result for a plan whose execution-plan descriptor could not be compiled.
inline AutotuneResult makeCompileFailedResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
const AutotuneConfig& config,
const std::string& errorMessage,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
return makeNonBenchmarkedResult(engineId,
knobSettings,
estimatedWorkspaceSize,
/*compiledWorkspaceSize=*/-1,
config,
errorMessage,
supportsExhaustive,
ranExhaustive,
exhaustiveNotRunReason);
}
// Failed result for a plan whose execution-plan descriptor could not be finalized.
inline AutotuneResult makeFinalizeFailedResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
const AutotuneConfig& config,
const std::string& errorMessage,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
return makeNonBenchmarkedResult(engineId,
knobSettings,
/*estimatedWorkspaceSize=*/-1,
/*compiledWorkspaceSize=*/-1,
config,
errorMessage,
supportsExhaustive,
ranExhaustive,
exhaustiveNotRunReason);
}
// Non-benchmarked result for a plan excluded by config.engineIdFilter.
inline AutotuneResult makeFilteredResult(int64_t engineId,
const std::vector<KnobSetting>& knobSettings,
int64_t estimatedWorkspaceSize,
int64_t compiledWorkspaceSize,
const AutotuneConfig& config,
bool supportsExhaustive,
bool ranExhaustive,
const std::string& exhaustiveNotRunReason)
{
return makeNonBenchmarkedResult(engineId,
knobSettings,
estimatedWorkspaceSize,
compiledWorkspaceSize,
config,
"Plan excluded by engineIdFilter.",
supportsExhaustive,
ranExhaustive,
exhaustiveNotRunReason);
}
// Copy knob settings while dropping the internal global.benchmarking knob,
// which is managed exclusively by autotune() in EXHAUSTIVE mode. Logs one
// warning per stripped knob, attributing it to callerName.
inline std::vector<KnobSetting> stripBenchmarkingKnob(const std::vector<KnobSetting>& settings,
const char* callerName)
{
std::vector<KnobSetting> stripped;
stripped.reserve(settings.size());
for(const auto& setting : settings)
{
if(setting.knobId() == BENCHMARKING_KNOB_NAME)
{
HIPDNN_FE_LOG_WARN("Stripping internal knob '"
<< BENCHMARKING_KNOB_NAME << "' from " << callerName << " call. "
<< "This knob is managed by autotune() in EXHAUSTIVE mode.");
continue;
}
stripped.push_back(setting);
}
return stripped;
}
// Rank benchmark results and select the winning plan.
//
// Sorts succeeded results (custom config.rankingFn if provided, otherwise by
// minTimeMs ascending), reassembles succeeded-then-failed, assigns 0-based
// ranks to succeeded results and -1 to failed ones, then sets activePlanIndex
// to the compiledPlanIndex of the first succeeded result. Returns a fatal error
// if no winner is found.
inline Error rankAndSelectWinner(std::vector<AutotuneResult>& allResults,
const AutotuneConfig& config,
size_t& activePlanIndex)
{
// Separate succeeded and failed results
std::vector<AutotuneResult> succeededResults;
std::vector<AutotuneResult> failedResults;
for(auto& r : allResults)
{
if(r.succeeded)
{
succeededResults.push_back(std::move(r));
}
else
{
failedResults.push_back(std::move(r));
}
}
if(config.rankingFn)
{
// Pass only succeeded results to the user's ranking function
try
{
config.rankingFn(succeededResults);
}
catch(const std::exception& e)
{
HIPDNN_FE_LOG_ERROR("autotune: custom ranking function threw an exception: "
<< e.what() << ". Falling back to default ranking.");
std::stable_sort(succeededResults.begin(),
succeededResults.end(),
[](const AutotuneResult& a, const AutotuneResult& b) {
return a.minTimeMs < b.minTimeMs;
});
}
catch(...)
{
HIPDNN_FE_LOG_WARN("autotune: custom ranking function threw an unknown exception. "
"Falling back to default ranking.");
std::stable_sort(succeededResults.begin(),
succeededResults.end(),
[](const AutotuneResult& a, const AutotuneResult& b) {
return a.minTimeMs < b.minTimeMs;
});
}
}
else
{
// Default ranking: succeeded engines by minTimeMs ascending
std::stable_sort(succeededResults.begin(),
succeededResults.end(),
[](const AutotuneResult& a, const AutotuneResult& b) {
return a.minTimeMs < b.minTimeMs;
});
}
const size_t succeededCount = succeededResults.size();
const size_t failedCount = failedResults.size();
// Reassemble succeeded-then-failed while assigning ranks and selecting the
// winner in a single pass. Succeeded results get 0-based ranks in sorted
// order; failed results get -1. The winner is the first succeeded result
// with a valid compiledPlanIndex, whose index sets the active plan directly
// (avoiding the fragile O(n*m) (engineId, knobSettings) search loop).
allResults.clear();
std::optional<AutotuneResult> winner;
for(size_t i = 0; i < succeededResults.size(); ++i)
{
succeededResults[i].rank = static_cast<int>(i);
// Defensive check; a succeeded result implies valid index.
if(!winner && succeededResults[i].compiledPlanIndex >= 0)
{
activePlanIndex = static_cast<size_t>(succeededResults[i].compiledPlanIndex);
winner = succeededResults[i];
}
allResults.push_back(std::move(succeededResults[i]));
}
for(auto& result : failedResults)
{
result.rank = -1;
allResults.push_back(std::move(result));
}
HIPDNN_FE_LOG_INFO("autotune: ranking complete - " << succeededCount << " succeeded, "
<< failedCount << " failed");
if(!winner)
{
return {ErrorCode::HIPDNN_BACKEND_ERROR,
"All engines failed during autotuning. No winner selected."};
}
HIPDNN_FE_LOG_INFO("autotune: winner - engine " << winner->engineName << " (ID "
<< winner->engineId
<< "), min=" << winner->minTimeMs << "ms");
return {ErrorCode::OK, ""};
}
// Synchronize the device between benchmarking phases.
//
// Uses a one-shot ProfilingControlDescriptor to call hipDeviceSynchronize()
// via the backend, keeping HIP calls out of the frontend header. Ensures the
// GPU is idle from previous work before the next phase starts. Returns a
// fatal error on any synchronization failure so the caller can mark the
// affected plan's timing as unreliable.
inline Error syncDevice()
{
// NOLINTNEXTLINE(misc-const-correctness)
::hipdnn_frontend::detail::ScopedHipdnnBackendDescriptor syncDesc(
HIPDNN_BACKEND_PROFILING_CONTROL_EXT);
if(!syncDesc.valid())
{
return {ErrorCode::HIPDNN_BACKEND_ERROR,
"autotune: failed to create sync descriptor for device synchronization"};
}
bool syncVal = true;
auto syncStatus = ::hipdnn_frontend::detail::hipdnnBackend()->backendSetAttribute(
syncDesc.get(), HIPDNN_ATTR_PROFILING_DEVICE_SYNC_EXT, HIPDNN_TYPE_BOOLEAN, 1, &syncVal);
if(syncStatus != HIPDNN_STATUS_SUCCESS)
{
return {ErrorCode::HIPDNN_BACKEND_ERROR, "autotune: device sync setAttribute failed"};
}
// No backendFinalize() needed: sync is triggered immediately by
// setAttribute(DEVICE_SYNC). finalize() would throw for sync-only
// descriptors (no start/stop events recorded).
return {ErrorCode::OK, ""};
}
} // namespace hipdnn_frontend::autotune::detail