Files
TheRock/rocm-libraries/projects/miopen/src/conv/solver_finders.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

575 lines
23 KiB
C++

// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <miopen/conv/solver_finders.hpp>
#include <algorithm>
#include <numeric>
#include <miopen/conv_algo_name.hpp>
#include <miopen/config.h>
#include <miopen/env.hpp>
#include <miopen/kernel_tuning_mode.hpp>
#include <miopen/mlo_internal.hpp>
#include <miopen/perf_field.hpp>
#include <miopen/conv/problem_description.hpp>
#include <miopen/solution.hpp>
#include <miopen/utility/modified_z.hpp>
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_GEMM)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_DIRECT)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_WINOGRAD)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_IMPLICIT_GEMM)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_FFT)
MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_DEVICE_ARCH)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_COMPILE_ONLY)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_FIND_CONV_INSUFFICIENT_WORKSPACE_ALLOW_FINDDB_UPDATE)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_NAIVE_DISABLE_IF_ALT, true)
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_SEARCH_CUTOFF, false)
MIOPEN_DECLARE_ENV_VAR_UINT64(MIOPEN_FIND_SKIP_PCT, 130)
MIOPEN_DECLARE_ENV_VAR_UINT64(MIOPEN_CONV_DIRECT_MAX_SIZE, 0)
namespace miopen {
namespace conv {
namespace {
class DirectSolverFinder : public SolversFinderMixin<ProblemDescription, ConvFindParameters>
{
protected:
AlgorithmName GetAlgorithmName(const ProblemDescription& problem) const override
{
return AlgorithmName{ConvolutionAlgoToDirectionalString(miopenConvolutionAlgoDirect,
problem.GetDirection())};
}
bool IsEnabled(const ExecutionContext& /*ctx*/,
const ProblemDescription& problem,
const ConvFindParameters& parameters) const override
{
return (!parameters.use_winograd_only &&
!IsAlgorithmDisabled(miopenConvolutionAlgoDirect, problem));
}
std::vector<solver::ConvSolution> FindImpl(const ExecutionContext& ctx,
const ProblemDescription& problem,
const AnyInvokeParams& invoke_ctx,
const ConvFindParameters&,
const std::optional<FindOptions>&) const override
{
/// \todo: actually use FindOptions
return problem.GetDirection() != conv::Direction::BackwardWeights
? FindAllDirectSolutions(ctx, problem, invoke_ctx)
: FindAllBwdWrW2DSolutions(ctx, problem, invoke_ctx);
}
};
class ImplicitGemmSolverFinder : public SolversFinderMixin<ProblemDescription, ConvFindParameters>
{
protected:
AlgorithmName GetAlgorithmName(const ProblemDescription& problem) const override
{
return AlgorithmName{ConvolutionAlgoToDirectionalString(miopenConvolutionAlgoImplicitGEMM,
problem.GetDirection())};
}
bool IsEnabled(const ExecutionContext& /*ctx*/,
const ProblemDescription& /*problem*/,
const ConvFindParameters& parameters) const override
{
return !parameters.use_winograd_only && !env::disabled(MIOPEN_DEBUG_CONV_IMPLICIT_GEMM);
}
std::vector<solver::ConvSolution> FindImpl(const ExecutionContext& ctx,
const ProblemDescription& problem,
const AnyInvokeParams& invoke_ctx,
const ConvFindParameters&,
const std::optional<FindOptions>&) const override
{
/// \todo: actually use FindOptions
return problem.GetDirection() != conv::Direction::BackwardWeights
? FindAllImplicitGemmSolutions(ctx, problem, invoke_ctx)
: FindImplicitGemmWrWAllSolutions(ctx, problem, invoke_ctx);
}
};
class FftSolverFinder : public SolversFinderMixin<ProblemDescription, ConvFindParameters>
{
protected:
AlgorithmName GetAlgorithmName(const ProblemDescription& problem) const override
{
return AlgorithmName{
ConvolutionAlgoToDirectionalString(miopenConvolutionAlgoFFT, problem.GetDirection())};
}
bool IsEnabled(const ExecutionContext& /*ctx*/,
const ProblemDescription& problem,
const ConvFindParameters& parameters) const override
{
return !parameters.use_winograd_only &&
problem.GetDirection() != conv::Direction::BackwardWeights &&
!env::disabled(MIOPEN_DEBUG_CONV_FFT);
}
std::vector<solver::ConvSolution> FindImpl(const ExecutionContext& ctx,
const ProblemDescription& problem,
const AnyInvokeParams& invoke_ctx,
const ConvFindParameters&,
const std::optional<FindOptions>&) const override
{
/// \todo: actually use FindOptions
return FindAllFFTSolutions(ctx, problem, invoke_ctx);
}
};
class GemmSolverFinder : public SolversFinderMixin<ProblemDescription, ConvFindParameters>
{
protected:
AlgorithmName GetAlgorithmName(const ProblemDescription& problem) const override
{
return AlgorithmName{
ConvolutionAlgoToDirectionalString(miopenConvolutionAlgoGEMM, problem.GetDirection())};
}
bool IsEnabled(const ExecutionContext& /*ctx*/,
const ProblemDescription& /*problem*/,
const ConvFindParameters& parameters) const override
{
return !parameters.use_winograd_only && !env::disabled(MIOPEN_DEBUG_CONV_GEMM);
}
std::vector<solver::ConvSolution> FindImpl(const ExecutionContext& ctx,
const ProblemDescription& problem,
const AnyInvokeParams& invoke_ctx,
const ConvFindParameters&,
const std::optional<FindOptions>&) const override
{
/// \todo: actually use FindOptions
return FindAllGemmSolutions(ctx, problem, invoke_ctx);
}
};
class WinogradSolverFinder : public SolversFinderMixin<ProblemDescription, ConvFindParameters>
{
protected:
AlgorithmName GetAlgorithmName(const ProblemDescription& problem) const override
{
return AlgorithmName{ConvolutionAlgoToDirectionalString(miopenConvolutionAlgoWinograd,
problem.GetDirection())};
}
bool IsEnabled(const ExecutionContext& /*ctx*/,
const ProblemDescription& /*problem*/,
const ConvFindParameters& /*parameters*/) const override
{
return !env::disabled(MIOPEN_DEBUG_CONV_WINOGRAD);
}
std::vector<solver::ConvSolution> FindImpl(const ExecutionContext& ctx,
const ProblemDescription& problem,
const AnyInvokeParams& invoke_ctx,
const ConvFindParameters& parameters,
const std::optional<FindOptions>&) const override
{
/// \todo: actually use FindOptions
auto ctx_copy = ctx;
if(parameters.use_winograd_only)
ctx_copy.use_dynamic_solutions_only = true;
return problem.GetDirection() != conv::Direction::BackwardWeights
? FindAllWinogradSolutions(ctx_copy, problem, invoke_ctx)
: FindWinogradWrWAllSolutions(ctx_copy, problem, invoke_ctx);
}
};
} // namespace
const std::vector<std::unique_ptr<ISolversFinder>>& GetConvSolverFinders()
{
static const auto finders = []() {
auto tmp = std::vector<std::unique_ptr<ISolversFinder>>{};
tmp.emplace_back(std::make_unique<ImplicitGemmSolverFinder>());
tmp.emplace_back(std::make_unique<GemmSolverFinder>());
tmp.emplace_back(std::make_unique<WinogradSolverFinder>());
tmp.emplace_back(std::make_unique<FftSolverFinder>());
tmp.emplace_back(std::make_unique<DirectSolverFinder>());
return tmp;
}();
return finders;
}
} // namespace conv
/// Register invoker only for the best solution within algorithm.
std::vector<Solution> EvaluateInvokers(const Handle& handle,
const std::vector<solver::ConvSolution>& solutions,
const AlgorithmName& algorithm_name,
const NetworkConfig& network_config,
const AnyInvokeParams& invoke_ctx,
FindCoreResult& core_result,
bool force_attach_binary,
bool& non_naive_succeeded)
{
std::vector<Solution> ret;
const auto arch = env::value(MIOPEN_DEVICE_ARCH);
if(!arch.empty())
return ret;
const auto is_naive_solver = [](const solver::ConvSolution& s) {
return s.solver_id.find("Naive") != std::string::npos;
};
bool naive_disable = env::value(MIOPEN_NAIVE_DISABLE_IF_ALT);
bool using_search_cutoff = env::value(MIOPEN_SEARCH_CUTOFF);
// Defer Naive only when a non-Naive alternative exists across all algorithms or this one.
const bool defer_naive =
naive_disable &&
(non_naive_succeeded || std::any_of(solutions.begin(), solutions.end(), [&](const auto& s) {
return !is_naive_solver(s);
}));
auto selected = miopen::solver::ConvSolution{miopenStatusUnknownError};
auto best = std::numeric_limits<float>::max();
auto best_invoker = Invoker{};
std::vector<float> samples;
// Iterate non-Naive solutions first, Naive last
std::vector<std::size_t> order(solutions.size());
std::iota(order.begin(), order.end(), 0);
if(defer_naive)
{
std::stable_partition(order.begin(), order.end(), [&](std::size_t i) {
return !is_naive_solver(solutions[i]);
});
}
for(std::size_t idx : order)
{
const auto& sol = solutions[idx];
const bool is_naive = is_naive_solver(sol);
if(naive_disable && is_naive)
{
if(defer_naive && non_naive_succeeded)
{
MIOPEN_LOG_I("Skipping Naive Solver: " << algorithm_name.ToString() << ":"
<< sol.solver_id);
continue;
}
MIOPEN_LOG_I("Unable to Skip Naive Solver: " << algorithm_name.ToString() << ":"
<< sol.solver_id);
}
if(!conv::IsEnoughWorkspace(
"EvaluateInvokers", solver::Id{sol.solver_id}, sol.workspace_sz, &invoke_ctx))
{
// Providing smaller workspace may result in the selection of a slow convolution
// algorithm, and therefore affect library performance. Moreover, sub-optimal data may
// be cached in the user's find-db. This means that the performance drop will become
// persistent, i.e. even providing sufficient workspace won't restore the performance.
// To get rid of this problem, the user will need to either remove the user's find-db,
// or repeat miopenFindConvolution*() with affected convolution configs in Normal Find
// Mode (the latter will overwrite sub-optimal user's find-db records).
//
// That is why we do not write sub-optimal results into persistent find-db (on disk)
// unless this is explicitly enabled via environment setting.
if(!env::enabled(MIOPEN_FIND_CONV_INSUFFICIENT_WORKSPACE_ALLOW_FINDDB_UPDATE))
core_result.is_optimal = false;
continue;
}
if(!sol.invoker_factory)
MIOPEN_THROW("Invoker is not provided by solver " + sol.solver_id);
float skip_time = core_result.find_search_best_time;
if(skip_time < std::numeric_limits<float>::max())
{
skip_time *= env::value(MIOPEN_FIND_SKIP_PCT) / 100.0f;
}
MIOPEN_LOG_I("Evaluating Solver: " << algorithm_name.ToString() << ":" << sol.solver_id);
std::vector<Program> programs;
const auto invoker = handle.PrepareInvoker(*sol.invoker_factory,
sol.construction_params,
force_attach_binary ? &programs : nullptr);
try
{
// Log solution name for grouped kernel logging
const auto solver_id_obj = solver::Id{sol.solver_id};
if(IsLoggingKernel())
{
LogSolutionName(sol.solver_id, solver_id_obj.Value(), sol.workspace_sz);
// Extract kernel name from first kernel in solution (if available)
std::string kernel_name;
if(!sol.construction_params.empty() &&
!sol.construction_params[0].kernel_name.empty())
{
kernel_name = sol.construction_params[0].kernel_name;
}
else
{
kernel_name = sol.solver_id; // Fallback to solver name
}
// Log performance config before timing runs. We don't have config descriptor so
// leave it blank.
AddPerformanceConfig(kernel_name, "");
}
// Run invoker max 8 times, with ~5 sec time limit.
using elapsed_t = decltype(handle.GetKernelTime());
constexpr elapsed_t TIME_MS_MAX = 5000.0;
constexpr int N_RUNS_MAX = 8;
auto elapsed = static_cast<elapsed_t>(0);
auto first_elapsed = static_cast<elapsed_t>(0);
int i = 0;
samples.clear();
while(i < N_RUNS_MAX && elapsed < TIME_MS_MAX)
{
invoker(handle, invoke_ctx);
// don't include warm-up run in our samples.
if(i > 0)
{
samples.push_back(handle.GetKernelTime());
if(i == 1 && using_search_cutoff && samples.front() > 1.0f &&
samples.front() > skip_time)
{
MIOPEN_LOG_I("Skipping (Slow) Solver: "
<< algorithm_name.ToString() << ":" << sol.solver_id << " "
<< samples.front() << " > " << skip_time);
break;
}
}
else
{
// Keep first run just in case we go over the limit, and have no samples.
first_elapsed = handle.GetKernelTime();
}
++i;
}
if(samples.size() > 0)
{
if(IsLoggingKernel())
{
// Update the performance config with the collected samples
AddInvokerTimes(samples);
}
// Remove outliers that are more than 2 positive modified z-score's away, and get
// the mean.
elapsed = miopen::removeHighOutliersAndGetMean(samples, 2.0f);
}
else
{
elapsed = first_elapsed;
}
MIOPEN_THROW_IF(elapsed <= 0, "Invalid elapsed time detected in EvaluateInvokers");
MIOPEN_LOG_I("solution(current vs best):" << sol << ": " << elapsed
<< (elapsed < best ? " < " : " >= ") << best);
if(elapsed < best)
{
best = elapsed;
selected = sol;
best_invoker = invoker;
if(best < core_result.find_search_best_time)
core_result.find_search_best_time = best;
}
auto solution = Solution{solver::Id{sol.solver_id}, elapsed, sol.workspace_sz};
if(force_attach_binary)
solution.SetInvoker(invoker, programs, selected.construction_params);
else
solution.SetInvoker(invoker, {}, {});
ret.emplace_back(std::move(solution));
if(!is_naive)
non_naive_succeeded = true;
}
catch(const miopen::Exception& ex)
{
MIOPEN_LOG_E(ex.what());
}
}
if(!selected.Succeeded())
{
ret.clear();
return ret;
}
handle.RegisterInvoker(best_invoker, network_config, selected.solver_id, algorithm_name);
MIOPEN_LOG_I("Selected: " << selected << ": " << best
<< ", workspace_sz = " << selected.workspace_sz);
return ret;
}
FindCoreResult FindCore(const AnyInvokeParams& invoke_ctx,
const ExecutionContext& ctx,
const ProblemDescriptionBase& problem,
const PrimitiveFindParameters& parameters,
const std::vector<std::unique_ptr<ISolversFinder>>& finders,
const std::optional<FindOptions>& options,
bool force_attach_binary)
{
auto& handle = ctx.GetStream();
// Find
auto solutions = std::vector<std::pair<AlgorithmName, std::vector<solver::ConvSolution>>>{};
std::transform(
finders.begin(), finders.end(), std::inserter(solutions, solutions.end()), [&](auto&& f) {
return std::make_pair(f->GetAlgorithmName(problem),
f->Find(ctx, problem, invoke_ctx, parameters, options));
});
std::size_t total = 0;
for(auto it = solutions.begin(); it != solutions.end();)
{
if(it->second.empty())
{
it = solutions.erase(it);
continue;
}
total += it->second.size();
++it;
}
// Precompile
{
auto all = std::vector<const miopen::solver::ConvSolution*>{};
all.reserve(total);
for(const auto& ss : solutions)
std::transform(ss.second.begin(),
ss.second.end(),
std::back_inserter(all),
[](auto&& s) { return &s; });
PrecompileSolutions(handle, all, force_attach_binary);
}
if(env::enabled((MIOPEN_DEBUG_COMPILE_ONLY)))
MIOPEN_THROW(
miopenStatusGpuOperationsSkipped,
"MIOPEN_DEBUG_COMPILE_ONLY is enabled, escaping forward convolution. Search skipped.");
// Evaluate Invokers
AutoEnableProfiling enableProfiling{handle};
const auto network_config = problem.MakeNetworkConfig();
auto ret = FindCoreResult();
ret.is_optimal = true;
ret.solutions.reserve(total);
bool non_naive_succeeded = false;
for(const auto& ss : solutions)
{
auto evaluated = EvaluateInvokers(handle,
ss.second,
ss.first,
network_config,
invoke_ctx,
ret,
force_attach_binary,
non_naive_succeeded);
ret.solutions.insert(ret.solutions.end(),
std::make_move_iterator(evaluated.begin()),
std::make_move_iterator(evaluated.end()));
}
return ret;
}
namespace conv {
namespace detail {
/// Determine if problem size exceeds threshold for Direct solver.
///
/// The result tensor is used to estimate problem size.
/// The maximum size is determined by MIOPEN_CONV_DIRECT_MAX_SIZE environment variable.
///
/// @param problem The convolution problem description.
bool IsDirectProblemTooLarge(const ProblemDescription& problem)
{
const unsigned long long max_size = env::value(MIOPEN_CONV_DIRECT_MAX_SIZE);
// 0 means no limit
if(max_size == 0)
return false;
// For FWD/BWD: 'out' is the result (swapped in BWD)
// For WRW: 'weights' is the result (out is dy, not dw)
const size_t problem_size = problem.IsDirectionBackwardWrW()
? problem.GetWeights().GetElementSize()
: problem.GetOut().GetElementSize();
// Problem size is within limit
if(problem_size <= max_size)
return false;
MIOPEN_LOG_I2("DirectSolverFinder disabled for problem size "
<< problem_size << " > " << max_size << " (MIOPEN_CONV_DIRECT_MAX_SIZE)");
return true;
}
} // namespace detail
bool IsAlgorithmDisabled(miopenConvAlgorithm_t algo, const ProblemDescription& problem)
{
switch(algo)
{ // clang-format off
case miopenConvolutionAlgoGEMM:
#if MIOPEN_USE_GEMM
return env::disabled(MIOPEN_DEBUG_CONV_GEMM);
#else
return true;
#endif
case miopenConvolutionAlgoDirect:
return env::disabled(MIOPEN_DEBUG_CONV_DIRECT) || detail::IsDirectProblemTooLarge(problem);
case miopenConvolutionAlgoFFT:
return env::disabled(MIOPEN_DEBUG_CONV_FFT);
case miopenConvolutionAlgoWinograd:
return env::disabled(MIOPEN_DEBUG_CONV_WINOGRAD);
case miopenConvolutionAlgoImplicitGEMM:
return env::disabled(MIOPEN_DEBUG_CONV_IMPLICIT_GEMM);
} // clang-format on
// Disable future algos by default to enforce explicit handling
return true;
}
bool IsEnoughWorkspace(std::string_view where,
const miopen::solver::Id& solver_id,
const std::size_t required_size,
const miopen::AnyInvokeParams* const invokeParams,
bool log_as_warning)
{
if(invokeParams != nullptr && required_size > 0)
{
const auto provided_size = invokeParams->GetWorkspaceSize();
const auto provided_ptr = invokeParams->GetWorkspace();
if(provided_ptr == nullptr || provided_size < required_size)
{
std::stringstream log;
log << "[" << where << "] Solver <" << solver_id.ToString() << ">"
<< ", workspace required: " << required_size << ", provided ptr: " << provided_ptr
<< " size: " << provided_size;
if(log_as_warning)
MIOPEN_LOG_W(log.str());
else
MIOPEN_LOG_I2(log.str());
return false;
}
}
return true;
}
} // namespace conv
} // namespace miopen