// 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 #include #include #include #include #include #include #include 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 dim; std::vector 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 viewsOf(const std::vector& ts) { std::vector views; views.reserve(ts.size()); for(const auto& t : ts) { views.push_back(viewOf(t)); } return views; } TensorPattern makePattern(std::vector dim) { TensorPattern p; p.dim = std::move(dim); return p; } TensorPattern makePatternWithStride(std::vector dim, std::vector stride) { TensorPattern p; p.dim = std::move(dim); p.stride = std::move(stride); return p; } TensorPattern makeNamedPattern(std::string tensorId, std::vector dim) { TensorPattern p; p.tensorId = std::move(tensorId); p.dim = std::move(dim); return p; } EngineOverrideConfig makeConfig(std::vector 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 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 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 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 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 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>{{1, 3, 224, 224}, {8, 64, 56, 56}, {32, 256, 14, 14}}) { const std::vector 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 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 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 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 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 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 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 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 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 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 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 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 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 matching{namedViewOf("x_tensor_uid", x), namedViewOf("w_tensor_uid", w)}; const std::vector 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 tensors1 = {{{1, 3, 224, 224}, {}}}; EXPECT_FALSE(config.matchOperation("conv_fprop", viewsOf(tensors1)).has_value()); const std::vector 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 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 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 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 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 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 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>{ {150528, 50176, 224, 1}, {1, 3, 672, 150528}, {999, 888, 777, 666}}) { const std::vector 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 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 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 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 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 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 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()); }