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
836 lines
31 KiB
C++
836 lines
31 KiB
C++
// Copyright © Advanced Micro Devices, Inc., or its affiliates.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
/**
|
||
* @file TestEngineOverrideConfig.cpp
|
||
* @brief Unit tests for the rule-matching internals of EngineOverrideConfig
|
||
* (op + dim + stride wildcards, exact/wildcard partition ordering,
|
||
* JSON parsing). The end-to-end policy behavior driven by
|
||
* HIPDNN_HEUR_CONFIG_PATH is covered in TestConfigBuiltIn.cpp.
|
||
*/
|
||
|
||
#include "heuristics/config/EngineOverrideConfig.hpp"
|
||
|
||
#include <gtest/gtest.h>
|
||
#include <hipdnn_data_sdk/detail/AutotuneConfigNames.hpp>
|
||
#include <hipdnn_data_sdk/utilities/EngineNames.hpp>
|
||
#include <nlohmann/json.hpp>
|
||
|
||
#include <cstdint>
|
||
#include <string>
|
||
#include <string_view>
|
||
#include <vector>
|
||
|
||
using namespace hipdnn_backend::heuristics::config;
|
||
using namespace hipdnn_data_sdk::utilities;
|
||
|
||
namespace
|
||
{
|
||
namespace config_criterion = hipdnn_data_sdk::detail::autotune_config::criterion;
|
||
// NOLINTNEXTLINE(misc-unused-alias-decls)
|
||
namespace config_json = hipdnn_data_sdk::detail::autotune_config::json;
|
||
namespace config_op = hipdnn_data_sdk::detail::autotune_config::op;
|
||
namespace config_tensor = hipdnn_data_sdk::detail::autotune_config::tensor;
|
||
// NOLINTNEXTLINE(misc-unused-alias-decls)
|
||
namespace config_version = hipdnn_data_sdk::detail::autotune_config::version;
|
||
|
||
struct TensorData
|
||
{
|
||
std::vector<int64_t> dim;
|
||
std::vector<int64_t> stride;
|
||
};
|
||
|
||
TensorView viewOf(const TensorData& t)
|
||
{
|
||
return TensorView{"", &t.dim, &t.stride};
|
||
}
|
||
|
||
TensorView namedViewOf(std::string_view tensorId, const TensorData& t)
|
||
{
|
||
return TensorView{tensorId, &t.dim, &t.stride};
|
||
}
|
||
|
||
std::vector<TensorView> viewsOf(const std::vector<TensorData>& ts)
|
||
{
|
||
std::vector<TensorView> views;
|
||
views.reserve(ts.size());
|
||
for(const auto& t : ts)
|
||
{
|
||
views.push_back(viewOf(t));
|
||
}
|
||
return views;
|
||
}
|
||
|
||
TensorPattern makePattern(std::vector<int64_t> dim)
|
||
{
|
||
TensorPattern p;
|
||
p.dim = std::move(dim);
|
||
return p;
|
||
}
|
||
|
||
TensorPattern makePatternWithStride(std::vector<int64_t> dim, std::vector<int64_t> stride)
|
||
{
|
||
TensorPattern p;
|
||
p.dim = std::move(dim);
|
||
p.stride = std::move(stride);
|
||
return p;
|
||
}
|
||
|
||
TensorPattern makeNamedPattern(std::string tensorId, std::vector<int64_t> dim)
|
||
{
|
||
TensorPattern p;
|
||
p.tensorId = std::move(tensorId);
|
||
p.dim = std::move(dim);
|
||
return p;
|
||
}
|
||
|
||
EngineOverrideConfig makeConfig(std::vector<OperationRule> rules)
|
||
{
|
||
return EngineOverrideConfig(std::move(rules));
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// ── Test 1: exact dim match, single rule ────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, ExactDimMatchSingleRule)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.tensors = {makePattern({1, 3, 224, 224}), makePattern({64, 3, 7, 7})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {}}, {{64, 3, 7, 7}, {}}};
|
||
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
// ── Test 2: first matching rule wins ────────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, FirstMatchingRuleWins)
|
||
{
|
||
OperationRule rule1;
|
||
rule1.op = "conv_fprop";
|
||
rule1.engineName = MIOPEN_ENGINE_NAME;
|
||
rule1.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
OperationRule rule2;
|
||
rule2.op = "conv_fprop";
|
||
rule2.engineName = HIPBLASLT_ENGINE_NAME;
|
||
rule2.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(rule1), std::move(rule2)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {}}};
|
||
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
// ── Test 3: no rule matches (wrong dims) ────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, NoRuleMatchesWrongDims)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 112, 112}, {}}};
|
||
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
EXPECT_FALSE(result.has_value());
|
||
}
|
||
|
||
// ── Test 4: wildcard (-1) in one dimension ──────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, WildcardInOneDimension)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = HIPBLASLT_ENGINE_NAME;
|
||
rule.tensors = {makePattern({-1, 64, 56, 56})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
for(const int64_t batch : {1, 4, 8, 32})
|
||
{
|
||
const std::vector<TensorData> tensors = {{{batch, 64, 56, 56}, {}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value()) << "batch=" << batch << " should match";
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
const std::vector<TensorData> tensors = {{{4, 128, 56, 56}, {}}};
|
||
EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(tensors)).has_value());
|
||
}
|
||
|
||
// ── Test 5: all-wildcard rule matches any shape ─────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, AllWildcardRuleMatchesAnyShape)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = HIP_MLOPS_ENGINE_NAME;
|
||
rule.tensors = {makePattern({-1, -1, -1, -1})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
for(const auto& shape :
|
||
std::vector<std::vector<int64_t>>{{1, 3, 224, 224}, {8, 64, 56, 56}, {32, 256, 14, 14}})
|
||
{
|
||
const std::vector<TensorData> tensors = {{shape, {}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIP_MLOPS_ENGINE_ID);
|
||
}
|
||
}
|
||
|
||
// ── Test 6: wrong op name → nullopt ─────────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, WrongOpNameReturnsNullopt)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {}}};
|
||
|
||
EXPECT_FALSE(config.matchOperation("conv_dgrad", viewsOf(tensors)).has_value());
|
||
EXPECT_FALSE(config.matchOperation("conv_wgrad", viewsOf(tensors)).has_value());
|
||
EXPECT_FALSE(config.matchOperation("matmul", viewsOf(tensors)).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, CriteriaMustMatch)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "pointwise";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.criteria = {Criterion{"pointwise_mode", 2}};
|
||
rule.tensors = {makePattern({2, 4, 16, 16}), makePattern({2, 4, 16, 16})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
const std::vector<TensorData> tensors = {{{2, 4, 16, 16}, {}}, {{2, 4, 16, 16}, {}}};
|
||
|
||
auto result
|
||
= config.matchOperation("pointwise", {Criterion{"pointwise_mode", 2}}, viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
|
||
EXPECT_FALSE(
|
||
config.matchOperation("pointwise", {Criterion{"pointwise_mode", 34}}, viewsOf(tensors))
|
||
.has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, CriteriaOrderIsNormalizedBeforeMatch)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = config_op::RESAMPLE_FWD;
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.criteria = {Criterion{config_criterion::RESAMPLE_MODE, 1},
|
||
Criterion{config_criterion::PADDING_MODE, 2}};
|
||
rule.tensors = {makePattern({2, 4, 16, 16})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
const std::vector<TensorData> tensors = {{{2, 4, 16, 16}, {}}};
|
||
|
||
auto result = config.matchOperation(config_op::RESAMPLE_FWD,
|
||
{Criterion{config_criterion::PADDING_MODE, 2},
|
||
Criterion{config_criterion::RESAMPLE_MODE, 1}},
|
||
viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, MissingCriteriaDoesNotMatchSpecificRule)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "pointwise";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.criteria = {Criterion{"pointwise_mode", 2}};
|
||
rule.tensors = {makePattern({2, 4, 16, 16})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
const std::vector<TensorData> tensors = {{{2, 4, 16, 16}, {}}};
|
||
|
||
EXPECT_FALSE(config.matchOperation("pointwise", viewsOf(tensors)).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, SameShapeDifferentCriteriaSelectsDifferentEngines)
|
||
{
|
||
OperationRule addRule;
|
||
addRule.op = "pointwise";
|
||
addRule.engineName = MIOPEN_ENGINE_NAME;
|
||
addRule.criteria = {Criterion{"pointwise_mode", 2}};
|
||
addRule.tensors = {makePattern({2, 4, 16, 16}), makePattern({2, 4, 16, 16})};
|
||
|
||
OperationRule mulRule;
|
||
mulRule.op = "pointwise";
|
||
mulRule.engineName = HIPBLASLT_ENGINE_NAME;
|
||
mulRule.criteria = {Criterion{"pointwise_mode", 30}};
|
||
mulRule.tensors = {makePattern({2, 4, 16, 16}), makePattern({2, 4, 16, 16})};
|
||
|
||
const auto config = makeConfig({std::move(addRule), std::move(mulRule)});
|
||
const std::vector<TensorData> tensors = {{{2, 4, 16, 16}, {}}, {{2, 4, 16, 16}, {}}};
|
||
|
||
auto addResult
|
||
= config.matchOperation("pointwise", {Criterion{"pointwise_mode", 2}}, viewsOf(tensors));
|
||
ASSERT_TRUE(addResult.has_value());
|
||
EXPECT_EQ(*addResult, MIOPEN_ENGINE_ID);
|
||
|
||
auto mulResult
|
||
= config.matchOperation("pointwise", {Criterion{"pointwise_mode", 30}}, viewsOf(tensors));
|
||
ASSERT_TRUE(mulResult.has_value());
|
||
EXPECT_EQ(*mulResult, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, NamedTensorRulesIgnoreConfigTensorOrder)
|
||
{
|
||
auto config = EngineOverrideConfig::loadFromContent(nlohmann::json{
|
||
{hipdnn_data_sdk::detail::autotune_config::json::VERSION,
|
||
hipdnn_data_sdk::detail::autotune_config::version::CURRENT},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::ENGINE_OVERRIDES,
|
||
nlohmann::json::array(
|
||
{{{hipdnn_data_sdk::detail::autotune_config::json::OP, config_op::POINTWISE},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::CRITERIA,
|
||
{{config_criterion::POINTWISE_MODE, 2}}},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::ENGINE_NAME, MIOPEN_ENGINE_NAME},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::TENSORS,
|
||
nlohmann::json::array(
|
||
{{{hipdnn_data_sdk::detail::autotune_config::json::TENSOR_ID,
|
||
config_tensor::IN_1},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::DIM, {5, 6}}},
|
||
{{hipdnn_data_sdk::detail::autotune_config::json::TENSOR_ID,
|
||
config_tensor::IN_0},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::DIM, {2, 4}}}})}}})}}
|
||
.dump());
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const TensorData x{{2, 4}, {}};
|
||
const TensorData y{{5, 6}, {}};
|
||
const std::vector<TensorView> tensors{namedViewOf(config_tensor::IN_0, x),
|
||
namedViewOf(config_tensor::IN_1, y)};
|
||
|
||
auto result = config->matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, tensors);
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, MissingVersionUsesLegacyPositionalTensorMatchingEvenWithTensorIds)
|
||
{
|
||
auto config = EngineOverrideConfig::loadFromContent(nlohmann::json{
|
||
{hipdnn_data_sdk::detail::autotune_config::json::ENGINE_OVERRIDES,
|
||
nlohmann::json::array(
|
||
{{{hipdnn_data_sdk::detail::autotune_config::json::OP, config_op::POINTWISE},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::CRITERIA,
|
||
{{config_criterion::POINTWISE_MODE, 2}}},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::ENGINE_NAME, MIOPEN_ENGINE_NAME},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::TENSORS,
|
||
nlohmann::json::array(
|
||
{{{hipdnn_data_sdk::detail::autotune_config::json::TENSOR_ID,
|
||
config_tensor::IN_1},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::DIM, {2, 4}}},
|
||
{{hipdnn_data_sdk::detail::autotune_config::json::TENSOR_ID,
|
||
config_tensor::IN_0},
|
||
{hipdnn_data_sdk::detail::autotune_config::json::DIM, {5, 6}}}})}}})}}
|
||
.dump());
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const TensorData x{{2, 4}, {}};
|
||
const TensorData y{{5, 6}, {}};
|
||
const std::vector<TensorView> tensors{namedViewOf(config_tensor::IN_0, x),
|
||
namedViewOf(config_tensor::IN_1, y)};
|
||
|
||
auto result = config->matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, tensors);
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, NewerVersionUsesNamedTensorMatching)
|
||
{
|
||
auto config = EngineOverrideConfig::loadFromContent(nlohmann::json{
|
||
{config_json::VERSION, config_version::CURRENT + 1},
|
||
{config_json::ENGINE_OVERRIDES,
|
||
nlohmann::json::array(
|
||
{{{config_json::OP, config_op::POINTWISE},
|
||
{config_json::CRITERIA, {{config_criterion::POINTWISE_MODE, 2}}},
|
||
{config_json::ENGINE_NAME, MIOPEN_ENGINE_NAME},
|
||
{config_json::TENSORS,
|
||
nlohmann::json::array(
|
||
{{{config_json::TENSOR_ID, config_tensor::IN_1}, {config_json::DIM, {5, 6}}},
|
||
{{config_json::TENSOR_ID, config_tensor::IN_0},
|
||
{config_json::DIM, {2, 4}}}})}}})}}.dump());
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const TensorData x{{2, 4}, {}};
|
||
const TensorData y{{5, 6}, {}};
|
||
const std::vector<TensorView> tensors{namedViewOf(config_tensor::IN_0, x),
|
||
namedViewOf(config_tensor::IN_1, y)};
|
||
|
||
auto result = config->matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, tensors);
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsDuplicateTensorIdsInNamedConfig)
|
||
{
|
||
auto config = EngineOverrideConfig::loadFromContent(nlohmann::json{
|
||
{config_json::VERSION, config_version::CURRENT},
|
||
{config_json::ENGINE_OVERRIDES,
|
||
nlohmann::json::array(
|
||
{{{config_json::OP, config_op::POINTWISE},
|
||
{config_json::CRITERIA, {{config_criterion::POINTWISE_MODE, 2}}},
|
||
{config_json::ENGINE_NAME, MIOPEN_ENGINE_NAME},
|
||
{config_json::TENSORS,
|
||
nlohmann::json::array(
|
||
{{{config_json::TENSOR_ID, config_tensor::IN_0}, {config_json::DIM, {2, 4}}},
|
||
{{config_json::TENSOR_ID, config_tensor::IN_0},
|
||
{config_json::DIM, {5, 6}}}})}}})}}.dump());
|
||
EXPECT_FALSE(config.has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsMissingTensorIdInNamedConfig)
|
||
{
|
||
auto config = EngineOverrideConfig::loadFromContent(nlohmann::json{
|
||
{config_json::VERSION, config_version::CURRENT},
|
||
{config_json::ENGINE_OVERRIDES,
|
||
nlohmann::json::array(
|
||
{{{config_json::OP, config_op::POINTWISE},
|
||
{config_json::CRITERIA, {{config_criterion::POINTWISE_MODE, 2}}},
|
||
{config_json::ENGINE_NAME, MIOPEN_ENGINE_NAME},
|
||
{config_json::TENSORS,
|
||
nlohmann::json::array(
|
||
{{{config_json::TENSOR_ID, config_tensor::IN_0}, {config_json::DIM, {2, 4}}},
|
||
{{config_json::DIM, {5, 6}}}})}}})}}.dump());
|
||
EXPECT_FALSE(config.has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, FirstRuleDimMismatchFallsThroughToSecondMatchingRule)
|
||
{
|
||
OperationRule first;
|
||
first.op = config_op::POINTWISE;
|
||
first.engineName = MIOPEN_ENGINE_NAME;
|
||
first.criteria = {Criterion{config_criterion::POINTWISE_MODE, 2}};
|
||
first.tensors = {makePattern({9, 9}), makePattern({5, 6})};
|
||
|
||
OperationRule second;
|
||
second.op = config_op::POINTWISE;
|
||
second.engineName = HIPBLASLT_ENGINE_NAME;
|
||
second.criteria = {Criterion{config_criterion::POINTWISE_MODE, 2}};
|
||
second.tensors = {makePattern({2, 4}), makePattern({5, 6})};
|
||
|
||
const auto config = makeConfig({std::move(first), std::move(second)});
|
||
const std::vector<TensorData> tensors = {{{2, 4}, {}}, {{5, 6}, {}}};
|
||
auto result = config.matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, FirstRuleCriteriaMismatchFallsThroughToSecondMatchingRule)
|
||
{
|
||
OperationRule first;
|
||
first.op = config_op::POINTWISE;
|
||
first.engineName = MIOPEN_ENGINE_NAME;
|
||
first.criteria = {Criterion{config_criterion::POINTWISE_MODE, 30}};
|
||
first.tensors = {makePattern({2, 4}), makePattern({5, 6})};
|
||
|
||
OperationRule second;
|
||
second.op = config_op::POINTWISE;
|
||
second.engineName = HIPBLASLT_ENGINE_NAME;
|
||
second.criteria = {Criterion{config_criterion::POINTWISE_MODE, 2}};
|
||
second.tensors = {makePattern({2, 4}), makePattern({5, 6})};
|
||
|
||
const auto config = makeConfig({std::move(first), std::move(second)});
|
||
const std::vector<TensorData> tensors = {{{2, 4}, {}}, {{5, 6}, {}}};
|
||
auto result = config.matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, FirstRuleNamedTensorRoleMismatchFallsThroughToSecondMatchingRule)
|
||
{
|
||
OperationRule first;
|
||
first.op = config_op::POINTWISE;
|
||
first.engineName = MIOPEN_ENGINE_NAME;
|
||
first.criteria = {Criterion{config_criterion::POINTWISE_MODE, 2}};
|
||
first.useNamedTensorIds = true;
|
||
first.tensors = {makeNamedPattern(config_tensor::IN_0, {5, 6}),
|
||
makeNamedPattern(config_tensor::IN_1, {2, 4})};
|
||
|
||
OperationRule second;
|
||
second.op = config_op::POINTWISE;
|
||
second.engineName = HIPBLASLT_ENGINE_NAME;
|
||
second.criteria = {Criterion{config_criterion::POINTWISE_MODE, 2}};
|
||
second.useNamedTensorIds = true;
|
||
second.tensors = {makeNamedPattern(config_tensor::IN_0, {2, 4}),
|
||
makeNamedPattern(config_tensor::IN_1, {5, 6})};
|
||
|
||
const auto config = makeConfig({std::move(first), std::move(second)});
|
||
const TensorData x{{2, 4}, {}};
|
||
const TensorData y{{5, 6}, {}};
|
||
const std::vector<TensorView> tensors{namedViewOf(config_tensor::IN_0, x),
|
||
namedViewOf(config_tensor::IN_1, y)};
|
||
auto result = config.matchOperation(
|
||
config_op::POINTWISE, {Criterion{config_criterion::POINTWISE_MODE, 2}}, tensors);
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, NamedTensorRulesRejectSameShapeWrongRole)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = config_op::SDPA_FWD;
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.useNamedTensorIds = true;
|
||
rule.tensors = {makeNamedPattern(config_tensor::Q, {2, 4}),
|
||
makeNamedPattern(config_tensor::K, {2, 4}),
|
||
makeNamedPattern(config_tensor::V, {2, 4}),
|
||
makeNamedPattern(config_tensor::SCALE, {1})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
const TensorData q{{2, 4}, {}};
|
||
const TensorData k{{2, 4}, {}};
|
||
const TensorData v{{2, 4}, {}};
|
||
const TensorData bias{{1}, {}};
|
||
const std::vector<TensorView> tensors{namedViewOf(config_tensor::Q, q),
|
||
namedViewOf(config_tensor::K, k),
|
||
namedViewOf(config_tensor::V, v),
|
||
namedViewOf(config_tensor::ATTN_MASK, bias)};
|
||
|
||
EXPECT_FALSE(config.matchOperation(config_op::SDPA_FWD, tensors).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LegacyRuleWithoutTensorIdsUsesPositionalOrder)
|
||
{
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"op": "conv_fprop",
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [
|
||
{ "dim": [1, 3, 4, 4] },
|
||
{ "dim": [2, 3, 1, 1] }
|
||
]
|
||
}
|
||
]
|
||
})";
|
||
|
||
auto config = EngineOverrideConfig::loadFromContent(CONTENTS);
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const TensorData x{{1, 3, 4, 4}, {}};
|
||
const TensorData w{{2, 3, 1, 1}, {}};
|
||
const std::vector<TensorView> matching{namedViewOf("x_tensor_uid", x),
|
||
namedViewOf("w_tensor_uid", w)};
|
||
const std::vector<TensorView> reversed{namedViewOf("w_tensor_uid", w),
|
||
namedViewOf("x_tensor_uid", x)};
|
||
|
||
EXPECT_TRUE(config->matchOperation("conv_fprop", matching).has_value());
|
||
EXPECT_FALSE(config->matchOperation("conv_fprop", reversed).has_value());
|
||
}
|
||
|
||
// ── Test 7: wrong tensor count in rule → nullopt ────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, WrongTensorCountReturnsNullopt)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.tensors = {makePattern({1, 3, 224, 224}), makePattern({64, 3, 7, 7})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
const std::vector<TensorData> tensors1 = {{{1, 3, 224, 224}, {}}};
|
||
EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(tensors1)).has_value());
|
||
|
||
const std::vector<TensorData> tensors3
|
||
= {{{1, 3, 224, 224}, {}}, {{64, 3, 7, 7}, {}}, {{64, 1, 1, 1}, {}}};
|
||
EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(tensors3)).has_value());
|
||
}
|
||
|
||
// ── Tests 11–12: cross-partition ordering (exact vs wildcard) ───────────────
|
||
|
||
TEST(TestEngineOverrideConfig, WildcardBeforeExactBothMatch)
|
||
{
|
||
OperationRule wildcard;
|
||
wildcard.op = "conv_fprop";
|
||
wildcard.engineName = HIP_MLOPS_ENGINE_NAME;
|
||
wildcard.tensors = {makePattern({-1, 3, 224, 224})};
|
||
|
||
OperationRule exact;
|
||
exact.op = "conv_fprop";
|
||
exact.engineName = HIPBLASLT_ENGINE_NAME;
|
||
exact.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(wildcard), std::move(exact)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIP_MLOPS_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, ExactBeforeWildcardBothMatch)
|
||
{
|
||
OperationRule exact;
|
||
exact.op = "conv_fprop";
|
||
exact.engineName = HIPBLASLT_ENGINE_NAME;
|
||
exact.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
OperationRule wildcard;
|
||
wildcard.op = "conv_fprop";
|
||
wildcard.engineName = HIP_MLOPS_ENGINE_NAME;
|
||
wildcard.tensors = {makePattern({-1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(exact), std::move(wildcard)});
|
||
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
// ── Stride matching tests ────────────────────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, ExactStrideMatchSelectsEngine)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = MIOPEN_ENGINE_NAME;
|
||
rule.tensors = {makePatternWithStride({1, 3, 224, 224}, {150528, 50176, 224, 1})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
const std::vector<TensorData> matching = {{{1, 3, 224, 224}, {150528, 50176, 224, 1}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(matching));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
|
||
const std::vector<TensorData> wrongStride
|
||
= {{{1, 3, 224, 224}, {1, 224, int64_t{224} * 3, int64_t{224} * 3 * 224}}};
|
||
EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(wrongStride)).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, WildcardStrideElement)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = HIPBLASLT_ENGINE_NAME;
|
||
rule.tensors = {makePatternWithStride({1, 3, 224, 224}, {150528, 50176, -1, -1})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
for(const int64_t s2 : {224, 112, 56})
|
||
{
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, {150528, 50176, s2, 1}}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value()) << "stride[2]=" << s2;
|
||
EXPECT_EQ(*result, HIPBLASLT_ENGINE_ID);
|
||
}
|
||
|
||
const std::vector<TensorData> wrongStride = {{{1, 3, 224, 224}, {999, 50176, 224, 1}}};
|
||
EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(wrongStride)).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, EmptyStridePatternMatchesAnyStride)
|
||
{
|
||
OperationRule rule;
|
||
rule.op = "conv_fprop";
|
||
rule.engineName = HIP_MLOPS_ENGINE_NAME;
|
||
rule.tensors = {makePattern({1, 3, 224, 224})};
|
||
|
||
const auto config = makeConfig({std::move(rule)});
|
||
|
||
for(const auto& strides : std::vector<std::vector<int64_t>>{
|
||
{150528, 50176, 224, 1}, {1, 3, 672, 150528}, {999, 888, 777, 666}})
|
||
{
|
||
const std::vector<TensorData> tensors = {{{1, 3, 224, 224}, strides}};
|
||
auto result = config.matchOperation("conv_fprop", viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, HIP_MLOPS_ENGINE_ID);
|
||
}
|
||
}
|
||
|
||
// ── JSON-dependent ──────────────────────────────────────────────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromValidJsonFile)
|
||
{
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"comment": "test rule for ResNet first conv",
|
||
"op": "conv_fprop",
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [
|
||
{ "dim": [1, 3, 224, 224] },
|
||
{ "dim": [64, 3, 7, 7] }
|
||
]
|
||
},
|
||
{
|
||
"comment": "wildcard catch-all",
|
||
"op": "conv_fprop",
|
||
"engine_name": "HIP_MLOPS_ENGINE",
|
||
"tensors": [
|
||
{ "dim": [-1, -1, -1, -1] },
|
||
{ "dim": [-1, -1, -1, -1] }
|
||
]
|
||
}
|
||
]
|
||
})";
|
||
|
||
auto config = EngineOverrideConfig::loadFromContent(CONTENTS);
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const std::vector<TensorData> exact = {{{1, 3, 224, 224}, {}}, {{64, 3, 7, 7}, {}}};
|
||
auto r1 = config->matchOperation("conv_fprop", viewsOf(exact));
|
||
ASSERT_TRUE(r1.has_value());
|
||
EXPECT_EQ(*r1, MIOPEN_ENGINE_ID);
|
||
|
||
const std::vector<TensorData> other = {{{8, 64, 56, 56}, {}}, {{64, 64, 3, 3}, {}}};
|
||
auto r2 = config->matchOperation("conv_fprop", viewsOf(other));
|
||
ASSERT_TRUE(r2.has_value());
|
||
EXPECT_EQ(*r2, HIP_MLOPS_ENGINE_ID);
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromJsonParsesCriteria)
|
||
{
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"op": "pointwise",
|
||
"criteria": { "pointwise_mode": 2 },
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [
|
||
{ "dim": [2, 4, 16, 16] },
|
||
{ "dim": [2, 4, 16, 16] }
|
||
]
|
||
}
|
||
]
|
||
})";
|
||
|
||
auto config = EngineOverrideConfig::loadFromContent(CONTENTS);
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const std::vector<TensorData> tensors = {{{2, 4, 16, 16}, {}}, {{2, 4, 16, 16}, {}}};
|
||
auto result
|
||
= config->matchOperation("pointwise", {Criterion{"pointwise_mode", 2}}, viewsOf(tensors));
|
||
ASSERT_TRUE(result.has_value());
|
||
EXPECT_EQ(*result, MIOPEN_ENGINE_ID);
|
||
EXPECT_FALSE(
|
||
config->matchOperation("pointwise", {Criterion{"pointwise_mode", 34}}, viewsOf(tensors))
|
||
.has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromMissingFileReturnsNullopt)
|
||
{
|
||
auto config = EngineOverrideConfig::load("/nonexistent/path/hipdnn_no_such_file.json");
|
||
EXPECT_FALSE(config.has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, EnvVarUnsetReturnsNullopt)
|
||
{
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromEnv().has_value());
|
||
}
|
||
|
||
// ── Malformed JSON: parser must return nullopt, not throw ──────────────────
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsInvalidJsonSyntax)
|
||
{
|
||
// Trailing comma + unterminated array — nlohmann::json::parse_error.
|
||
constexpr const char* CONTENTS = R"({ "engine_overrides": [)";
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromContent(CONTENTS).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsMissingTopLevelKey)
|
||
{
|
||
// Valid JSON, but no "engine_overrides" key — at() throws out_of_range.
|
||
constexpr const char* CONTENTS = R"({ "other_key": [] })";
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromContent(CONTENTS).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsMissingEntryFields)
|
||
{
|
||
// engine_overrides entry missing "engine_name" — at() throws out_of_range.
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{ "op": "conv_fprop", "tensors": [ { "dim": [1, 3, 224, 224] } ] }
|
||
]
|
||
})";
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromContent(CONTENTS).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsMissingTensorDim)
|
||
{
|
||
// tensor entry missing "dim" — at() throws out_of_range.
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"op": "conv_fprop",
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [ { "stride": [1, 2, 3, 4] } ]
|
||
}
|
||
]
|
||
})";
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromContent(CONTENTS).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, LoadFromContentRejectsWrongFieldType)
|
||
{
|
||
// "op" is a number, not a string — get<string> throws type_error.
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"op": 42,
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [ { "dim": [1, 3, 224, 224] } ]
|
||
}
|
||
]
|
||
})";
|
||
EXPECT_FALSE(EngineOverrideConfig::loadFromContent(CONTENTS).has_value());
|
||
}
|
||
|
||
TEST(TestEngineOverrideConfig, JsonWithStrideConstraint)
|
||
{
|
||
constexpr const char* CONTENTS = R"({
|
||
"engine_overrides": [
|
||
{
|
||
"op": "conv_fprop",
|
||
"engine_name": "MIOPEN_ENGINE",
|
||
"tensors": [
|
||
{ "dim": [1, 3, 224, 224], "stride": [150528, 50176, 224, 1] },
|
||
{ "dim": [64, 3, 7, 7] }
|
||
]
|
||
}
|
||
]
|
||
})";
|
||
|
||
auto config = EngineOverrideConfig::loadFromContent(CONTENTS);
|
||
ASSERT_TRUE(config.has_value());
|
||
|
||
const std::vector<TensorData> matching
|
||
= {{{1, 3, 224, 224}, {150528, 50176, 224, 1}}, {{64, 3, 7, 7}, {}}};
|
||
auto r1 = config->matchOperation("conv_fprop", viewsOf(matching));
|
||
ASSERT_TRUE(r1.has_value());
|
||
EXPECT_EQ(*r1, MIOPEN_ENGINE_ID);
|
||
|
||
const std::vector<TensorData> wrong
|
||
= {{{1, 3, 224, 224}, {1, 224, int64_t{224} * 3, int64_t{224} * 3 * 224}},
|
||
{{64, 3, 7, 7}, {}}};
|
||
EXPECT_FALSE(config->matchOperation("conv_fprop", viewsOf(wrong)).has_value());
|
||
}
|