Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/migraphx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ add_library(migraphx-ep MODULE
mgx_interop.h
mgx_kernel_reg.cc
mgx_kernel_reg.h
mgx_mlss_heuristics.cc
mgx_mlss_heuristics.h
mgx_options.h
mgx_utils.cc
mgx_utils.h
Expand Down
83 changes: 74 additions & 9 deletions src/migraphx/mgx_ep.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cctype>
#include <charconv>
#include <chrono>
#include <cstdio>
Expand Down Expand Up @@ -38,6 +39,7 @@
#include "mgx_ep_ctx.h"
#include "mgx_hip_graph.h"
#include "mgx_info.h"
#include "mgx_mlss_heuristics.h"
#include "mgx_precompile.h"
#include "mgx_program_ops.h"
#include "mgx_utils.h"
Expand Down Expand Up @@ -73,6 +75,50 @@ struct HipDeviceGuard {
HipDeviceGuard(const HipDeviceGuard&) = delete;
HipDeviceGuard& operator=(const HipDeviceGuard&) = delete;
};
struct arch_graph_mlss_exception {
std::string_view arch_prefix;
std::string_view graph_id;
};
constexpr std::array<arch_graph_mlss_exception, 4> kMlssGraphExceptions{{
// This ResNet-50 graph id differs across the ORT 1.26 and 1.27 partitioning paths.
{"gfx1200", "3a0532e672db5cf"},
{"gfx1201", "3a0532e672db5cf"},
{"gfx1200", "c4c5e56652ffbf28"},
{"gfx1201", "c4c5e56652ffbf28"},
}};

bool GraphIdListContains(std::string_view list, std::string_view graph_id) {
while (!list.empty()) {
const auto pos{list.find(',')};
auto entry{list.substr(0, pos)};
while (!entry.empty() && std::isspace(static_cast<unsigned char>(entry.front()))) {
entry.remove_prefix(1);
}
while (!entry.empty() && std::isspace(static_cast<unsigned char>(entry.back()))) {
entry.remove_suffix(1);
}
if (!entry.empty() && entry == graph_id) {
return true;
}
if (pos == std::string_view::npos) {
break;
}
list.remove_prefix(pos + 1);
}
return false;
}

bool MlssExcludedForGraph(std::string_view gfx, std::string_view graph_id, std::string_view extra_ids) {
if (GraphIdListContains(extra_ids, graph_id)) {
return true;
}
for (const auto& row : kMlssGraphExceptions) {
if (row.graph_id == graph_id && gfx.substr(0, row.arch_prefix.size()) == row.arch_prefix) {
return true;
}
}
return false;
}

// TEMPORARY A/B gate (env ORT_MIGRAPHX_LEGACY_COMPUTE_SYNC). When true, Compute
// keeps the legacy unconditional per-fused-node hipStreamSynchronize. When false
Expand Down Expand Up @@ -675,6 +721,9 @@ ExecutionProvider::ExecutionProvider(const ProviderFactory& factory, std::string
{"gfx1201", "conv"},
}};

mlss_requested_explicitly_ = !mlss_use_specific_ops_.empty();
PARSE_ENV_VAR(env_var::kMlssExcludeGraphIds, mlss_exclude_graph_ids_);

for (const auto& [arch, ops] : kArchMlssOps) {
if (compute_capability_.rfind(arch, 0) == 0) {
if (!mlss_use_specific_ops_.empty()) {
Expand Down Expand Up @@ -1192,6 +1241,21 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
Ort::Graph sorted_graph{graph.GetGraphView(sorted_nodes)};
ONNX_NAMESPACE::ModelProto model_proto{};
RETURN_IF_ERROR(GraphToProto(sorted_graph, model_proto));
const auto mlss_graph_features{AnalyzeMlssGraph(model_proto)};
const auto graph_id{GenerateGraphId(graph)};
std::string effective_mlss_use_specific_ops{
!mlss_use_specific_ops_.empty()
? mlss_use_specific_ops_
: (ShouldAutoForceMlssConv(compute_capability_, mlss_graph_features) ? "conv" : "")};
if (!mlss_requested_explicitly_ && !effective_mlss_use_specific_ops.empty() &&
MlssExcludedForGraph(compute_capability_, graph_id, mlss_exclude_graph_ids_)) {
ORT_CXX_LOGF_NOEXCEPT(logger_, ORT_LOGGING_LEVEL_INFO,
"[mgx-mlss] graph %s is opted out of automatic AMDMLSS ('%s') on %s",
graph_id.c_str(), effective_mlss_use_specific_ops.c_str(), compute_capability_.c_str());
effective_mlss_use_specific_ops.clear();
}
const std::string effective_mxr_prefix{
mxr_prefix + hash::ToHex(std::string_view{effective_mlss_use_specific_ops}) + "-"};
std::string onnx_string;
if (!model_proto.SerializeToString(&onnx_string) || onnx_string.empty()) {
return Ort::Status{"Serializing a model proto to string failed!", ORT_EP_FAIL};
Expand Down Expand Up @@ -1246,7 +1310,7 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
if (!use_plan_cache) {
fs::path mxr_path;
if (!effective_cache_dir.empty()) {
mxr_path = effective_cache_dir / (mxr_prefix + input_shapes_hash_hex + ".mxr");
mxr_path = effective_cache_dir / (effective_mxr_prefix + input_shapes_hash_hex + ".mxr");
}
loaded_from_cache = !force_recompile_ && load_compiled_program(program, mxr_path);
backend_telemetry_.loaded_from_cache = loaded_from_cache;
Expand All @@ -1258,7 +1322,7 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
migraphx::program_parameters params;
calibrate_and_quantize(program, t_, params, enable_fp16_, enable_bf16_, enable_int8_,
enable_fp8_, int8_calibration_cache_available_, dynamic_ranges_);
compile_program(program, t_, exhaustive_tune_, mlss_use_specific_ops_, compute_mode_,
compile_program(program, t_, exhaustive_tune_, effective_mlss_use_specific_ops, compute_mode_,
problem_cache_paths_);
// context_enable needs this file on disk even if caching is otherwise disabled.
if (!disable_compiled_model_caching_ || context_enable_) {
Expand All @@ -1274,7 +1338,7 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr

if (context_enable_) {
// input_shapes_hash_hex is non-empty here: the RETURN_IF above requires has_input_shape.
const fs::path ep_context_mxr_path{mxr_prefix + input_shapes_hash_hex + ".mxr"};
const fs::path ep_context_mxr_path{effective_mxr_prefix + input_shapes_hash_hex + ".mxr"};

EpContextNodeHelper ep_context_helper{*this, sorted_graph, fused_node};
RETURN_IF_ERROR(ep_context_helper.CreateEpContextNode(ep_context_mxr_path, effective_cache_dir,
Expand All @@ -1297,7 +1361,7 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
has_input_shape,
dump_subgraphs_,
exhaustive_tune_,
mlss_use_specific_ops_,
effective_mlss_use_specific_ops,
dynamic_ranges_,
input_name_indices,
output_name_indices,
Expand All @@ -1308,7 +1372,7 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
disable_compiled_model_caching_,
force_recompile_,
external_data_dir_,
mxr_prefix,
effective_mxr_prefix,
problem_cache_paths_,
});

Expand Down Expand Up @@ -1369,13 +1433,14 @@ Ort::Status ExecutionProvider::CreateNodeComputeInfoFromGraph(const Ort::ConstGr
compute_state.defer_compilation = true;
if (use_plan_cache) {
RETURN_IF_ERROR(PreloadMxrPrograms(pre_plan, input_name_indices, compute_state.cached_programs,
force_recompile_, effective_cache_dir, mxr_prefix));
force_recompile_, effective_cache_dir, effective_mxr_prefix));
if (precompile_at_load_) {
RETURN_IF_ERROR(CompileMissingPrograms(pre_plan, input_name_indices, onnx_string,
compute_state.cached_programs, t_, enable_fp16_, enable_bf16_, enable_int8_, enable_fp8_,
int8_calibration_cache_available_, dynamic_ranges_, exhaustive_tune_, mlss_use_specific_ops_,
compute_mode_, problem_cache_paths_, disable_compiled_model_caching_, model_path,
external_data_dir_, effective_cache_dir, mxr_prefix));
int8_calibration_cache_available_, dynamic_ranges_, exhaustive_tune_,
effective_mlss_use_specific_ops, compute_mode_, problem_cache_paths_,
disable_compiled_model_caching_, model_path, external_data_dir_, effective_cache_dir,
effective_mxr_prefix));
}
if (!compute_state.cached_programs.empty()) {
compute_state.program = SelectDefaultProgram(compute_state.cached_programs, pre_bucketed,
Expand Down
7 changes: 7 additions & 0 deletions src/migraphx/mgx_ep.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ constexpr auto kCompileBatches = "ORT_MIGRAPHX_COMPILE_BATCHES"sv;
constexpr auto kPrecompileAtLoad = "ORT_MIGRAPHX_PRECOMPILE_AT_LOAD"sv;
constexpr auto kCoalesceIO = "ORT_MIGRAPHX_COALESCE_IO"sv;
constexpr auto kMlssUseSpecificOps = "ORT_MIGRAPHX_MLSS_USE_SPECIFIC_OPS"sv;
// Comma-separated graph ids that skip automatic AMDMLSS
constexpr auto kMlssExcludeGraphIds = "ORT_MIGRAPHX_MLSS_EXCLUDE_GRAPH_IDS"sv;
constexpr auto kCpuControlFlow = "ORT_MIGRAPHX_CPU_CONTROL_FLOW"sv;
constexpr auto kModelArch = "ORT_MIGRAPHX_MODEL_ARCH"sv;
constexpr auto kStaticPadSeq = "ORT_MIGRAPHX_STATIC_PAD_SEQ"sv;
Expand Down Expand Up @@ -555,6 +557,11 @@ struct ExecutionProvider : OrtEp, ApiPtrs {
bool enable_int8_{};
bool exhaustive_tune_{};
std::string mlss_use_specific_ops_{};
// True when mlss_use_specific_ops_ was set by a provider option or environment variable,
// before the per-architecture default was applied
bool mlss_requested_explicitly_{};
// Extra graph ids from kMlssExcludeGraphIds
std::string mlss_exclude_graph_ids_{};
// Ordered read-only problem-cache paths (app override, then DLL-adjacent shipped),
// JSON-escaped for backend-option delivery.
std::vector<std::string> problem_cache_paths_{};
Expand Down
157 changes: 157 additions & 0 deletions src/migraphx/mgx_mlss_heuristics.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT

#include "mgx_mlss_heuristics.h"

#include <algorithm>
#include <limits>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace mgx_ep {
namespace {

std::uint64_t SaturatingMultiply(std::uint64_t left, std::uint64_t right) {
if (left == 0 || right == 0) {
return 0;
}
if (left > std::numeric_limits<std::uint64_t>::max() / right) {
return std::numeric_limits<std::uint64_t>::max();
}
return left * right;
}

template <typename Range>
std::uint64_t PositiveProduct(const Range& values, std::size_t begin = 0) {
if (begin >= static_cast<std::size_t>(values.size())) {
return 0;
}
std::uint64_t result{1};
for (std::size_t index = begin; index < static_cast<std::size_t>(values.size()); ++index) {
if (values[index] <= 0) {
return 0;
}
result = SaturatingMultiply(result,
static_cast<std::uint64_t>(values[index]));
}
return result;
}

const ONNX_NAMESPACE::AttributeProto* FindAttribute(
const ONNX_NAMESPACE::NodeProto& node, std::string_view name) {
for (const auto& attribute : node.attribute()) {
if (attribute.name() == name) {
return &attribute;
}
}
return nullptr;
}

std::uint64_t AttributeProduct(
const ONNX_NAMESPACE::NodeProto& node, std::string_view name) {
const auto* attribute{FindAttribute(node, name)};
if (attribute == nullptr || attribute->ints().empty()) {
return 1;
}
return PositiveProduct(attribute->ints());
}

} // namespace

MlssGraphFeatures AnalyzeMlssGraph(const ONNX_NAMESPACE::ModelProto& model) {
MlssGraphFeatures features{};
const auto& graph{model.graph()};
features.node_count = static_cast<std::uint64_t>(graph.node_size());
std::unordered_map<std::string, const ONNX_NAMESPACE::TensorProto*> tensors;
tensors.reserve(static_cast<std::size_t>(graph.initializer_size() + graph.node_size()));
for (const auto& initializer : graph.initializer()) {
tensors.emplace(initializer.name(), &initializer);
}
for (const auto& node : graph.node()) {
if (node.op_type() != "Constant" || node.output().empty()) {
continue;
}
if (const auto* value{FindAttribute(node, "value")}; value != nullptr && value->has_t()) {
tensors.emplace(node.output(0), &value->t());
}
}

std::unordered_set<std::string> initializer_names;
initializer_names.reserve(static_cast<std::size_t>(graph.initializer_size()));
for (const auto& initializer : graph.initializer()) {
initializer_names.insert(initializer.name());
}
// Skip initializer-as-input entries from old Caffe-converted graphs so
// channel/spatial stats describe activations, not weight tensors.
for (const auto& input : graph.input()) {
if (initializer_names.count(input.name()) != 0) {
continue;
}
if (!input.type().has_tensor_type() || !input.type().tensor_type().has_shape()) {
continue;
}
const auto& dimensions{input.type().tensor_type().shape().dim()};
std::vector<std::int64_t> shape;
shape.reserve(static_cast<std::size_t>(dimensions.size()));
for (const auto& dimension : dimensions) {
shape.push_back(dimension.has_dim_value() ? dimension.dim_value() : 0);
}
features.input_elements_max =
std::max(features.input_elements_max, PositiveProduct(shape));
if (shape.size() >= 2 && shape[1] > 0) {
features.input_channels_max =
std::max(features.input_channels_max, static_cast<std::uint64_t>(shape[1]));
}
if (shape.size() >= 4) {
features.input_spatial_max =
std::max(features.input_spatial_max, PositiveProduct(shape, 2));
}
}

for (const auto& node : graph.node()) {
if (node.op_type() != "Conv" || node.input_size() < 2) {
continue;
}
const auto tensor{tensors.find(node.input(1))};
if (tensor == tensors.end() || tensor->second->dims_size() < 3) {
continue;
}
const auto& weight{*tensor->second};
const std::uint64_t group = [&] {
const auto* attribute{FindAttribute(node, "group")};
return attribute != nullptr && attribute->i() > 0
? static_cast<std::uint64_t>(attribute->i())
: std::uint64_t{1};
}();
const auto weight_elements{PositiveProduct(weight.dims())};
const auto kernel_area{PositiveProduct(weight.dims(), 2)};
const auto output_channels{
static_cast<std::uint64_t>(std::max<std::int64_t>(weight.dims(0), 0))};
const auto input_channels_per_group{
static_cast<std::uint64_t>(std::max<std::int64_t>(weight.dims(1), 0))};
const auto input_channels{SaturatingMultiply(input_channels_per_group, group)};

++features.convolution_count;
features.convolution_weight_elements += weight_elements;
features.convolution_weight_elements_max =
std::max(features.convolution_weight_elements_max, weight_elements);
features.input_channels_sum += input_channels;
features.output_channels_sum += output_channels;
features.channels_max =
std::max(features.channels_max, std::max(input_channels, output_channels));
features.kernel_area_sum += kernel_area;
features.one_by_one_count += kernel_area == 1;
features.three_by_three_count += kernel_area == 9;
features.strided_count += AttributeProduct(node, "strides") > 1;
features.dilated_count += AttributeProduct(node, "dilations") > 1;
features.grouped_count += group > 1;
features.depthwise_count += group > 1 && group == input_channels;
features.fp16_count += weight.data_type() == ONNX_NAMESPACE::TensorProto_DataType_FLOAT16;
features.fp32_count += weight.data_type() == ONNX_NAMESPACE::TensorProto_DataType_FLOAT;
}
return features;
}

} // namespace mgx_ep
Loading