Files
TheRock/rocm-libraries/dnn-providers/integration-tests/src/harness/TestSettings.hpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

323 lines
11 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#pragma once
#include <algorithm>
#include <filesystem>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <hipdnn_test_sdk/utilities/ArchMatch.hpp>
#include "common/PlatformUtils.hpp"
// HIP's host_defines.h redefines __noinline__ as either
// __attribute__((noinline)) or empty, which collides with tomlplusplus's
// TOML_HAS_ATTR(__noinline__) macro. Temporarily undefine it around the
// toml.hpp include so the tomlplusplus preprocessor logic compiles cleanly
// under clang-tidy (which processes #if operands before short-circuit).
#ifdef __noinline__
#pragma push_macro("__noinline__")
#undef __noinline__
#include <toml.hpp>
#pragma pop_macro("__noinline__")
#else
#include <toml.hpp>
#endif
namespace hipdnn_integration_tests
{
struct ToleranceOverride
{
float atol;
float rtol;
};
// Loads a per-engine TOML settings file for integration tests.
// Currently supports tolerance overrides and arch-scoped test skips;
// additional settings (knobs, support matrix, etc.) will be added in
// future versions.
//
// TOML format:
// [meta]
// version = 1
//
// [[tolerance_overrides]]
// filters = ["*convolution*fp16*"]
// atol = 1e-3
// rtol = 1e-2
//
// [[test_skips]]
// archs = ["gfx1100", "gfx1101"]
// platforms = ["windows"] # optional
// filters = ["*Activation*Fp16*"]
// reason = "missing activation kernel on Windows + RDNA3"
//
// # Global skip (omit 'archs' and 'platforms' to skip everywhere)
// [[test_skips]]
// filters = ["*KnownBroken*"]
// reason = "tracked in #1234"
//
// Filters use GTest-style globs (globMatch with * wildcards).
// For tolerance_overrides, later entries take precedence over earlier
// ones when multiple filters match.
// For test_skips, an entry matches when ALL of:
// - 'archs' is omitted/empty (any arch), OR any 'archs' value is a
// substring of the device's raw gcnArchName.
// - 'platforms' is omitted/empty (any platform), OR any 'platforms'
// value exactly equals the current platform ("windows" or "linux").
// - any 'filters' glob matches the gtest-formatted test name.
// The first matching entry wins.
class TestSettings
{
public:
// Load config from a TOML file. Throws on parse errors or invalid format.
explicit TestSettings(const std::filesystem::path& configPath)
{
auto table = toml::parse_file(configPath.string());
validateMetaVersion(table, configPath);
if(auto* arr = table["tolerance_overrides"].as_array())
{
for(const auto& node : *arr)
{
_overrides.push_back(parseToleranceOverride(node));
}
}
if(auto* arr = table["test_skips"].as_array())
{
for(const auto& node : *arr)
{
_skips.push_back(parseTestSkip(node));
}
}
}
// Find a tolerance override matching the given test name.
// Returns the last matching override (later entries take precedence).
// Returns std::nullopt if no filter matches.
std::optional<ToleranceOverride> findToleranceOverride(std::string_view testName) const
{
std::optional<ToleranceOverride> result;
const std::string testNameStr(testName);
for(const auto& entry : _overrides)
{
for(const auto& filter : entry.filters)
{
if(globMatch(filter, testNameStr))
{
result = ToleranceOverride{entry.atol, entry.rtol};
break; // matched this entry, continue to next for precedence
}
}
}
return result;
}
size_t toleranceOverrideCount() const
{
return _overrides.size();
}
// Find a skip rule matching the given test name, device, and platform.
// deviceArchRaw is the unparsed gcnArchName string (e.g. "gfx942:sramecc+:xnack-").
// platform is the lowercase platform name ("windows" or "linux").
// An entry matches when:
// - the entry's 'archs' list is empty (any arch), OR any 'archs' value
// is a substring of deviceArchRaw, AND
// - the entry's 'platforms' list is empty (any platform), OR any
// 'platforms' value exactly equals platform, AND
// - any 'filters' glob matches testName.
// First matching entry wins; returns its reason. Returns nullopt if no
// entry matches.
std::optional<std::string> findSkip(std::string_view testName,
std::string_view deviceArchRaw,
std::string_view platform) const
{
const std::string testNameStr(testName);
const std::string deviceArchStr(deviceArchRaw);
const std::string platformStr(platform);
for(const auto& entry : _skips)
{
// Loose match: a skip entry's arch may be a family prefix (e.g.
// "gfx10" covers all gfx10xx), matched as a substring of the
// device's raw gcnArchName.
const bool archMatched
= entry.archs.empty()
|| std::any_of(
entry.archs.begin(), entry.archs.end(), [&](const std::string& candidate) {
return hipdnn_test_sdk::utilities::archMatches(
deviceArchStr,
candidate,
hipdnn_test_sdk::utilities::ArchMatchMode::SUBSTRING);
});
if(!archMatched)
{
continue;
}
const bool platformMatches
= entry.platforms.empty()
|| std::any_of(entry.platforms.begin(),
entry.platforms.end(),
[&](const std::string& p) { return p == platformStr; });
if(!platformMatches)
{
continue;
}
const bool filterMatches
= std::any_of(entry.filters.begin(),
entry.filters.end(),
[&](const std::string& f) { return globMatch(f, testNameStr); });
if(filterMatches)
{
return entry.reason;
}
}
return std::nullopt;
}
size_t skipEntryCount() const
{
return _skips.size();
}
private:
struct OverrideEntry
{
std::vector<std::string> filters;
float atol;
float rtol;
};
struct SkipEntry
{
std::vector<std::string> archs;
std::vector<std::string> platforms;
std::vector<std::string> filters;
std::string reason;
};
static void validateMetaVersion(const toml::table& table,
const std::filesystem::path& configPath)
{
auto version = table["meta"]["version"].value<int64_t>();
if(!version.has_value())
{
throw std::runtime_error("TestSettings: missing [meta].version in "
+ configPath.string());
}
if(*version != 1)
{
throw std::runtime_error("TestSettings: unsupported version " + std::to_string(*version)
+ " in " + configPath.string() + " (expected 1)");
}
}
// Parse a TOML string array. Returns the parsed strings, or an empty
// vector if the key is missing. Throws if a value is present but is
// not a string. Callers that require the array to be non-empty must
// check the result themselves.
static std::vector<std::string>
parseStringArray(const toml::table& entry, std::string_view key, const char* sectionName)
{
std::vector<std::string> result;
auto* arr = entry[key].as_array();
if(arr == nullptr)
{
return result;
}
for(const auto& node : *arr)
{
auto str = node.value<std::string>();
if(!str.has_value())
{
throw std::runtime_error(std::string(sectionName) + ": '" + std::string(key)
+ "' value must be a string");
}
result.push_back(*str);
}
return result;
}
static OverrideEntry parseToleranceOverride(const toml::node& node)
{
static constexpr const char* K_SECTION = "TestSettings: [[tolerance_overrides]]";
const auto* table = node.as_table();
if(table == nullptr)
{
throw std::runtime_error(std::string(K_SECTION) + " entry is not a table");
}
OverrideEntry parsed;
parsed.filters = parseStringArray(*table, "filters", K_SECTION);
if(parsed.filters.empty())
{
throw std::runtime_error(std::string(K_SECTION) + " entry missing 'filters' array");
}
auto atol = (*table)["atol"].value<double>();
if(!atol.has_value())
{
throw std::runtime_error(std::string(K_SECTION) + " entry missing 'atol'");
}
parsed.atol = static_cast<float>(*atol);
auto rtol = (*table)["rtol"].value<double>();
if(!rtol.has_value())
{
throw std::runtime_error(std::string(K_SECTION) + " entry missing 'rtol'");
}
parsed.rtol = static_cast<float>(*rtol);
return parsed;
}
static SkipEntry parseTestSkip(const toml::node& node)
{
static constexpr const char* K_SECTION = "TestSettings: [[test_skips]]";
const auto* table = node.as_table();
if(table == nullptr)
{
throw std::runtime_error(std::string(K_SECTION) + " entry is not a table");
}
SkipEntry parsed;
// 'archs' and 'platforms' are both optional. Empty = matches any.
parsed.archs = parseStringArray(*table, "archs", K_SECTION);
parsed.platforms = parseStringArray(*table, "platforms", K_SECTION);
parsed.filters = parseStringArray(*table, "filters", K_SECTION);
if(parsed.filters.empty())
{
throw std::runtime_error(std::string(K_SECTION) + " entry missing 'filters' array");
}
auto reason = (*table)["reason"].value<std::string>();
if(!reason.has_value() || reason->empty())
{
throw std::runtime_error(std::string(K_SECTION) + " entry missing 'reason'");
}
parsed.reason = std::move(*reason);
return parsed;
}
std::vector<OverrideEntry> _overrides;
std::vector<SkipEntry> _skips;
};
} // namespace hipdnn_integration_tests