Files
TheRock/rocm-libraries/projects/miopen/test/gtest/db_sync.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

1059 lines
44 KiB
C++

/*******************************************************************************
*
* MIT License
*
* Copyright (c) 2023 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*******************************************************************************/
#include <gtest/gtest.h>
#include <miopen/miopen.h>
#include "get_handle.hpp"
#include "../lib_env_var.hpp"
#include "gtest_common.hpp"
#include <miopen/readonlyramdb.hpp>
#include <miopen/env.hpp>
#include <miopen/execution_context.hpp>
#include <miopen/find_db.hpp>
#include <miopen/tensor.hpp>
#include <miopen/conv/problem_description.hpp>
#include <miopen/conv_algo_name.hpp>
#include <miopen/solver_id.hpp>
#include <miopen/any_solver.hpp>
#include <miopen/mt_queue.hpp>
#include <miopen/filesystem.hpp>
#include <cstdlib>
#include <regex>
#include <exception>
#include <thread>
#include <unordered_set>
/// \todo HACK
/// This should be set to 1 if either WORKAROUND_ISSUE_2492_GRANULARITY_LOSS
/// or WORKAROUND_ISSUE_2492_TINY_TENSOR is defined as non-zero in
/// src/solver/conv_winoRxS.cpp
#define WORKAROUND_ISSUE_2492 1
#if WORKAROUND_ISSUE_2492
MIOPEN_LIB_ENV_VAR(MIOPEN_DEBUG_WORKAROUND_ISSUE_2492)
#endif
#define WORKAROUND_ISSUE_1987 0 // Allows testing FDB on gfx1030 (legacy fdb).
#define SKIP_KDB_TESTING 1 // Disable KernelDB assertions in DBSync.
#define SKIP_PDB_TESTING 0 // Disable PerfDB assertions in DBSync.
#define SKIP_CONVHIPDIRECTFWDFUSED 0 // Allows testing FDB on gfx1030 (legacy fdb).
namespace fs = miopen::fs;
namespace env = miopen::env;
MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DBSYNC_CLEAN)
struct KDBKey
{
fs::path program_file;
std::string program_args;
bool operator==(const KDBKey& other) const
{
return (program_file == other.program_file) && (program_args == other.program_args);
}
};
template <>
struct std::hash<KDBKey>
{
std::size_t operator()(const KDBKey& k) const
{
return std::hash<std::string>()(k.program_file.string()) ^
(hash<string>()(k.program_args) << 1) >> 1;
}
};
#if WORKAROUND_ISSUE_1987
/// \todo Copied from src/db_record.cpp
/// Transform find-db (v.1.0) ID:VALUES to the current format.
/// Implementation is intentionally straightforward.
/// Do not include the 1st value from VALUES (solver name) into transformed VALUES.
/// Ignore FdbKCache_Key pair (last two values).
/// Append id (algorithm) to VALUES.
/// Use solver name as ID.
static bool TransformFindDbItem10to20(std::string& id, std::string& values)
{
MIOPEN_LOG_T("Legacy find-db item: " << id << ':' << values);
std::size_t pos = values.find(',');
if(pos == std::string::npos)
return false;
const auto solver = values.substr(0, pos);
const auto time_workspace_pos = pos + 1;
pos = values.find(',', time_workspace_pos);
if(pos == std::string::npos)
return false;
pos = values.find(',', pos + 1);
if(pos == std::string::npos)
return false;
const auto time_workspace = values.substr(time_workspace_pos, pos - time_workspace_pos);
values = time_workspace + ',' + id;
id = solver;
MIOPEN_LOG_T("Transformed find-db item: " << id << ':' << values);
return true;
}
#endif
namespace miopen {
conv::Direction GetDirectionFromString(const std::string& direction)
{
if(direction == "F")
return conv::Direction::Forward;
else if(direction == "B")
return conv::Direction::BackwardData;
else if(direction == "W")
return conv::Direction::BackwardWeights;
throw std::runtime_error("Invalid Direction");
}
miopenTensorLayout_t GetLayoutFromString(const std::string& layout)
{
if(layout == "NCHW")
return miopenTensorNCHW;
else if(layout == "NHWC")
return miopenTensorNHWC;
else if(layout == "NCDHW")
return miopenTensorNCDHW;
else if(layout == "NDHWC")
return miopenTensorNDHWC;
throw std::runtime_error("Invalid Layout");
}
miopenDataType_t GetDataTypeFromString(const std::string& data_type)
{
if(data_type == "FP32")
return miopenFloat;
else if(data_type == "FP16")
return miopenHalf;
else if(data_type == "INT8")
return miopenInt8;
else if(data_type == "INT32")
return miopenInt32;
else if(data_type == "BF16")
return miopenBFloat16;
else if(data_type == "FP64")
return miopenDouble;
throw std::runtime_error("Invalid data type in find db key");
}
void ParseProblemKey(const std::string& key_, conv::ProblemDescription& prob_desc)
{
std::string key = key_;
const auto opt = SplitDelim(key, '_');
int group_cnt = 1;
conv::Direction dir;
miopenTensorLayout_t in_layout, wei_layout, out_layout;
size_t out_h, out_w, in_channels, out_channels, in_h, in_w, batchsize, fil_h, fil_w;
int pad_h, pad_w, conv_stride_h, conv_stride_w, dil_h, dil_w;
miopenDataType_t precision;
TensorDescriptor in{};
TensorDescriptor wei{};
TensorDescriptor out{};
ConvolutionDescriptor conv;
bool use_tf32 = false;
if(opt.size() >= 2)
{
key = opt[0];
ASSERT_TRUE(StartsWith(opt[1], "g"));
group_cnt = std::stoi(RemovePrefix(opt[1], "g"));
}
else
ASSERT_TRUE(opt.size() == 1); // either there is one optional args or there is none
// 2d or 3d ?
const auto is_3d = [&]() {
const auto pat_3d = std::regex{"[0-9]x[0-9]x[0-9]"};
return std::regex_search(key, pat_3d);
}();
const auto attrs = SplitDelim(key, '-');
const auto sz = attrs.size();
dir = GetDirectionFromString(attrs[sz - 1]);
precision = GetDataTypeFromString(attrs[sz - 2]);
if(!is_3d)
{
ASSERT_TRUE(sz == 15 || sz == 17);
std::tie(in_layout, wei_layout, out_layout) = [&]() {
if(sz == 15) // same layout for all tensors
return std::tuple{GetLayoutFromString(attrs[12]),
GetLayoutFromString(attrs[12]),
GetLayoutFromString(attrs[12])};
else if(sz == 17)
return std::tuple{GetLayoutFromString(attrs[12]),
GetLayoutFromString(attrs[13]),
GetLayoutFromString(attrs[14])};
throw std::runtime_error{"FDB key parsing error"};
}();
in_channels = std::stoi(attrs[0]);
in_h = std::stoi(attrs[1]);
in_w = std::stoi(attrs[2]);
out_channels = std::stoi(attrs[4]);
out_h = std::stoi(attrs[5]);
out_w = std::stoi(attrs[6]);
batchsize = std::stoi(attrs[7]);
const auto split_tensor = [](const std::string& s) {
const auto tmp = miopen::SplitDelim(s, 'x');
EXPECT_TRUE(tmp.size() == 2) << "Two Dimensional problems need to have two dimensional "
"filters, pads, strides and dilations"; // for 2d keys
return std::tuple(std::stoi(tmp[0]), std::stoi(tmp[1]));
};
std::tie(fil_h, fil_w) = split_tensor(attrs[3]);
std::tie(pad_h, pad_w) = split_tensor(attrs[8]);
std::tie(conv_stride_h, conv_stride_w) = split_tensor(attrs[9]);
std::tie(dil_h, dil_w) = split_tensor(attrs[10]);
// construct the problem, serialize it and verify the output
in = TensorDescriptor{precision, in_layout, {batchsize, in_channels, in_h, in_w}};
if(dir == conv::Direction::Forward)
wei = TensorDescriptor(
precision, wei_layout, {out_channels, in_channels / group_cnt, fil_h, fil_w});
else
wei = TensorDescriptor(
precision, wei_layout, {in_channels, out_channels / group_cnt, fil_h, fil_w});
out = TensorDescriptor{precision, out_layout, {batchsize, out_channels, out_h, out_w}};
conv =
ConvolutionDescriptor{{pad_h, pad_w}, {conv_stride_h, conv_stride_w}, {dil_h, dil_w}};
}
else
{
int pad_d, conv_stride_d, dil_d;
size_t in_d, out_d, fil_d;
// 3D case
ASSERT_TRUE(sz == 17 || sz == 19);
std::tie(in_layout, wei_layout, out_layout) = [&]() {
if(sz == 17) // same layout for all tensors
return std::tuple{GetLayoutFromString(attrs[14]),
GetLayoutFromString(attrs[14]),
GetLayoutFromString(attrs[14])};
else // if(sz == 19)
return std::tuple{GetLayoutFromString(attrs[14]),
GetLayoutFromString(attrs[15]),
GetLayoutFromString(attrs[16])};
}();
in_channels = std::stoi(attrs[0]);
in_d = std::stoi(attrs[1]);
in_h = std::stoi(attrs[2]);
in_w = std::stoi(attrs[3]);
out_channels = std::stoi(attrs[5]);
out_d = std::stoi(attrs[6]);
out_h = std::stoi(attrs[7]);
out_w = std::stoi(attrs[8]);
batchsize = std::stoi(attrs[9]);
const auto split_tensor = [](const std::string& s) {
const auto tmp = miopen::SplitDelim(s, 'x');
EXPECT_TRUE(tmp.size() == 3) << "For a 3D problem, filters, pads, strides and "
"dilations need to be 3D as well"; // for 3d keys
return std::tuple(std::stoi(tmp[0]), std::stoi(tmp[1]), std::stoi(tmp[2]));
};
std::tie(fil_d, fil_h, fil_w) = split_tensor(attrs[4]);
std::tie(pad_d, pad_h, pad_w) = split_tensor(attrs[10]);
std::tie(conv_stride_d, conv_stride_h, conv_stride_w) = split_tensor(attrs[11]);
std::tie(dil_d, dil_h, dil_w) = split_tensor(attrs[12]);
// construct the problem, serialize it and verify the output
in = TensorDescriptor{precision, in_layout, {batchsize, in_channels, in_d, in_h, in_w}};
if(dir == conv::Direction::Forward)
wei = TensorDescriptor(precision,
wei_layout,
{out_channels, in_channels / group_cnt, fil_d, fil_h, fil_w});
else
wei = TensorDescriptor(precision,
wei_layout,
{in_channels, out_channels / group_cnt, fil_d, fil_h, fil_w});
out =
TensorDescriptor{precision, out_layout, {batchsize, out_channels, out_d, out_h, out_w}};
conv = ConvolutionDescriptor{{pad_d, pad_h, pad_w},
{conv_stride_d, conv_stride_h, conv_stride_w},
{dil_d, dil_h, dil_w},
std::vector<int>(3, 0),
1,
1.0};
conv::ProblemDescription tmp{in, wei, out, conv, dir};
}
conv.group_count = group_cnt;
if(precision == miopenFloat)
{
const auto math_type_ = use_tf32 ? miopenMathDefault : miopenMathPedantic;
conv.attribute.Set(MIOPEN_CONVOLUTION_ATTRIB_MATH_TYPE, static_cast<int>(math_type_));
}
prob_desc = conv::ProblemDescription{in, wei, out, conv, dir};
}
struct FDBVal
{
std::string solver_id;
std::string vals;
};
void ParseFDBbVal(const std::string& val, std::vector<FDBVal>& fdb_vals)
{
std::string id_val;
std::stringstream ss{val};
while(std::getline(ss, id_val, ';'))
{
const auto id_size = id_val.find(':');
ASSERT_TRUE(id_size != std::string::npos) << "Ill formed value: " << id_val;
auto id = id_val.substr(0, id_size);
auto values = id_val.substr(id_size + 1);
#if WORKAROUND_ISSUE_1987
/// \todo Copied from src/db_record.cpp
/// Detect legacy find-db item (v.1.0 ID:VALUES) and transform it to the current format.
/// For now, *only* legacy find-db record use convolution algorithm as ID, so if ID is
/// a valid algorithm, then we can safely assume that the item is in legacy format.
if(IsValidConvolutionDirAlgo(id))
{
ASSERT_TRUE(TransformFindDbItem10to20(id, values))
<< "Ill-formed legacy find-db item: " << values;
}
#endif
const auto tmp = FDBVal{id, values};
fdb_vals.emplace_back(tmp);
}
}
void GetPerfDbVals(const fs::path& filename,
const conv::ProblemDescription& problem_config,
std::unordered_map<std::string, std::string>& vals,
std::string& key,
std::string& select_query)
{
#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB
std::string clause;
std::vector<std::string> values;
std::tie(clause, values) = problem_config.WhereClause();
auto sql = SQLite{filename.string(), true};
// clang-format off
select_query =
"SELECT solver, params "
"FROM perf_db "
"INNER JOIN " + problem_config.table_name() + " "
"ON perf_db.config = " + problem_config.table_name() +".id "
"WHERE "
"( " + clause + " );";
// clang-format on
auto stmt = SQLite::Statement{sql, select_query, values};
while(true)
{
auto rc = stmt.Step(sql);
if(rc == SQLITE_ROW)
vals.emplace(stmt.ColumnText(0), stmt.ColumnText(1));
else if(rc == SQLITE_DONE)
break;
else if(rc == SQLITE_ERROR || rc == SQLITE_MISUSE)
throw std::runtime_error(sql.ErrorMessage());
}
#else
const auto& perf_db =
miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::PerfDb, filename.string(), true);
const auto& perf_db_map = perf_db.GetCacheMap();
auto& perf_db_rw = miopen::RamDb::GetCached(miopen::DbKinds::PerfDb, filename.string(), false);
std::ostringstream ss;
conv::ProblemDescription::VisitAll(problem_config, [&](auto&& value, auto&&) {
if(ss.tellp() != 0)
ss << "x";
ss << value;
});
key = ss.str();
if(perf_db_map.find(key) != perf_db_map.end())
{
bool duplicate = false;
std::istringstream pdb_line{perf_db_map.at(key).content};
char fragment[1024];
while(pdb_line.getline(fragment, 1024, ';'))
{
std::string id_val{fragment};
const auto id_size = id_val.find(':');
ASSERT_TRUE(id_size != std::string::npos) << "Ill formed value: " << id_val;
auto id = id_val.substr(0, id_size);
auto cfg = id_val.substr(id_size + 1);
if(env::enabled(MIOPEN_DBSYNC_CLEAN) && vals.find(id) != vals.end())
{
duplicate = true;
MIOPEN_LOG_E("Duplicate ID: " << id << "; key: " << key);
continue;
}
else
{
EXPECT_TRUE(vals.count(id) == 0)
<< "Duplicate ID in perf DB: " << id << "; key: " << key;
}
vals.emplace(id, cfg);
}
if(env::enabled(MIOPEN_DBSYNC_CLEAN) && duplicate)
{
MIOPEN_LOG_W("Rewrite Record at key: " << key);
const auto record = *perf_db_rw.FindRecord(problem_config);
perf_db_rw.StoreRecord(record);
}
select_query = " Loading " + key + " from " + filename.string();
}
#endif
}
void RemovePerfDbEntry(const fs::path& filename,
const conv::ProblemDescription& problem_config,
const std::string& solver)
{
std::string select_query;
std::string clause;
std::vector<std::string> values;
std::tie(clause, values) = problem_config.WhereClause();
auto sql = SQLite{filename.string(), true};
// clang-format off
select_query =
"DELETE FROM perf_db "
"WHERE "
"config in (select id from " + problem_config.table_name() + " "
"WHERE ( " + clause + " )) AND "
"solver='" + solver + "';";
// clang-format on
auto stmt = SQLite::Statement{sql, select_query, values};
while(true)
{
auto rc = stmt.Step(sql);
if(rc == SQLITE_DONE)
break;
else if(rc == SQLITE_ERROR || rc == SQLITE_MISUSE)
throw std::runtime_error(sql.ErrorMessage());
}
}
auto LoadKDBObjects(const fs::path& filename)
{
std::unordered_set<KDBKey> kdb_cache;
auto select_query = "SELECT kernel_name, kernel_args from kern_db";
auto sql = SQLite{filename.string(), true};
auto stmt = SQLite::Statement{sql, select_query};
int count = 0;
std::cout << "Loading kdb entries into cache" << std::endl;
while(true)
{
auto rc = stmt.Step(sql);
if(rc == SQLITE_ROW)
{
++count;
const auto kernel_name = stmt.ColumnText(0);
const auto kernel_args = stmt.ColumnText(1);
kdb_cache.emplace(KDBKey{kernel_name, kernel_args});
}
else if(rc == SQLITE_DONE)
break;
else if(rc == SQLITE_ERROR || rc == SQLITE_MISUSE)
throw std::runtime_error(sql.ErrorMessage());
if(count % 2000 == 0)
std::cout << "Loaded " << count << " entries from KDB" << std::endl;
}
std::cout << "Done loading " << count << " entries from kdb file: " << filename << std::endl;
return kdb_cache;
}
bool CheckKDBObjects(const fs::path& filename,
const fs::path& kernel_name,
const std::string& kernel_args)
{
static const auto kdb_cache = LoadKDBObjects(filename);
return kdb_cache.find(KDBKey{kernel_name, kernel_args}) != kdb_cache.end();
}
bool CheckKDBForTargetID(const fs::path& filename)
{
// clang-format off
auto select_query = "SELECT count(*) FROM kern_db WHERE ( kernel_args like '-mcpu=%sram-ecc%') OR (kernel_args like '-mcpu=%xnack%')";
// clang-format on
auto sql = SQLite{filename.string(), true};
auto stmt = SQLite::Statement{sql, select_query};
int count = 0;
while(true)
{
auto rc = stmt.Step(sql);
if(rc == SQLITE_ROW)
count = stmt.ColumnInt64(0);
else if(rc == SQLITE_DONE)
break;
else if(rc == SQLITE_ERROR || rc == SQLITE_MISUSE)
throw std::runtime_error(sql.ErrorMessage());
}
return count != 0;
}
bool CheckKDBJournalMode(const fs::path& filename)
{
auto journal_query = "PRAGMA journal_mode";
auto sql = SQLite{filename.string(), true};
auto stmt = SQLite::Statement{sql, journal_query};
std::string journal_mode;
while(true)
{
auto rc = stmt.Step(sql);
if(rc == SQLITE_ROW)
journal_mode = stmt.ColumnText(0);
else if(rc == SQLITE_DONE)
break;
else if(rc == SQLITE_ERROR || rc == SQLITE_MISUSE)
throw std::runtime_error(sql.ErrorMessage());
}
return journal_mode.compare("off") == 0 || journal_mode.compare("delete") == 0;
}
} // namespace miopen
void SetupPaths(fs::path& fdb_file_path,
fs::path& pdb_file_path,
fs::path& kdb_file_path,
const miopen::Handle& handle)
{
const std::string ext = ".fdb.txt";
const auto root_path = miopen::GetSystemDbPath();
// The base name has to be the test name for each GPU arch we have
const std::string base_name = handle.GetDbBasename(); // "gfx90a68";
const std::string suffix = "HIP"; // miopen::GetSystemFindDbSuffix();
fdb_file_path = root_path / (base_name + "." + suffix + ext);
#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB
pdb_file_path = root_path / (base_name + ".db");
#else
pdb_file_path = root_path / (base_name + ".db.txt");
#endif
kdb_file_path = root_path / (handle.GetDeviceName() + ".kdb");
ASSERT_TRUE(fs::exists(fdb_file_path)) << "Db file does not exist" << fdb_file_path;
ASSERT_TRUE(fs::exists(pdb_file_path)) << "Db file does not exist" << pdb_file_path;
ASSERT_TRUE(SKIP_KDB_TESTING || fs::exists(kdb_file_path))
<< "Db file does not exist" << kdb_file_path;
}
TEST(CPU_DBSync_NONE, KDBTargetID)
{
#if SKIP_KDB_TESTING
GTEST_SKIP();
#elif defined(__SANITIZE_ADDRESS__) || (defined(__has_feature) && __has_feature(address_sanitizer))
// Skip kernel db test when AddressSanitizer is enabled as we have not built and cached
// the kernels with AddressSanitizer enabled.
GTEST_SKIP();
#else
// Skip this test for gfx11 and gfx12 to avoid test failure (we don't have databases for those
// devices yet)
const auto& handle = get_handle();
if(miopen::StartsWith(handle.GetDeviceName(), "gfx11") ||
miopen::StartsWith(handle.GetDeviceName(), "gfx12"))
{
GTEST_SKIP();
}
fs::path fdb_file_path, pdb_file_path, kdb_file_path;
#if WORKAROUND_ISSUE_2492
ScopedEnvironment<std::string> issue_2492_env(MIOPEN_DEBUG_WORKAROUND_ISSUE_2492, "0");
#endif
SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, get_handle());
std::ignore = fdb_file_path;
std::ignore = pdb_file_path;
EXPECT_TRUE(miopen::CheckKDBJournalMode(kdb_file_path));
EXPECT_FALSE(!SKIP_KDB_TESTING && miopen::CheckKDBForTargetID(kdb_file_path));
#endif
}
bool LogBuildMessage()
{
MIOPEN_LOG_W("Unable to produce missing binary due to COMGR being enabled");
return true;
}
void BuildKernel(const fs::path& program_file,
const std::string& program_args,
[[maybe_unused]] const miopen::Handle& handle)
{
// Build the code object entry
// This will write the code object in the user kdb which Jenkins can archive
// This has to be done with the offline clang compiler and not COMGR (or hipRTC) otherwise the
// code object would be target ID specific
#if MIOPEN_USE_COMGR
static const bool discard = LogBuildMessage();
std::ignore = discard;
std::ignore = program_file;
std::ignore = program_args;
#else
try
{
auto p = handle.LoadProgram(program_file, program_args, "");
}
catch(std::exception&)
{
MIOPEN_LOG_W("Exception thrown while building kernel");
}
#endif
}
using FDBLine = std::pair<std::string, miopen::ReadonlyRamDb::CacheItem>;
void CheckDynamicFDBEntry(size_t thread_index,
size_t total_threads,
const std::vector<FDBLine>& find_data,
const miopen::ExecutionContext& _ctx,
std::atomic<size_t>& counter)
{
fs::path fdb_file_path, pdb_file_path, kdb_file_path;
auto& handle = _ctx.GetStream();
SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, handle);
std::unordered_set<KDBKey> checked_kdbs;
// Get list of dynamic solvers
std::vector<miopen::solver::Id> dyn_solvers;
for(const auto id :
miopen::solver::GetSolversByPrimitive(miopen::solver::Primitive::Convolution))
{
const auto solv = id.GetSolver();
if(solv.IsDynamic())
{
std::cout << id.ToString() << "Is Dynamic" << std::endl;
dyn_solvers.push_back(id);
}
}
const auto data_size = find_data.size();
for(auto kidx = thread_index; kidx < data_size; kidx += total_threads)
{
auto ctx = _ctx;
const auto& kinder = find_data[kidx];
miopen::conv::ProblemDescription problem;
miopen::ParseProblemKey(kinder.first, problem);
problem.SetupFloats(ctx); // TODO: Check if this is necessary
problem.SetupComputeType(ctx);
std::stringstream ss;
problem.Serialize(ss);
ASSERT_TRUE(ss.str() == kinder.first)
<< "Failed to parse FDB key:" << kidx << ":Parsed Key: " << ss.str();
// Check the kernels for all dynamic solvers exist
for(const auto& id : dyn_solvers)
{
const auto solv = id.GetSolver();
if(solv.IsApplicable(_ctx, problem))
{
auto db = miopen::GetDb(_ctx);
miopen::solver::ConvSolution sol = solv.FindSolution(_ctx, problem, db, {});
EXPECT_TRUE(sol.Succeeded())
<< "Applicable solver generated invalid solution fdb-key:" << kinder.first
<< " Solver: " << id.ToString();
for(const auto& kern : sol.construction_params)
{
std::string compile_options = kern.comp_options;
auto program_file = miopen::make_object_file_name(kern.kernel_file);
ASSERT_TRUE(kern.kernel_file.extension() != ".mlir")
<< "MLIR detected in dynamic solvers";
compile_options += " -mcpu=" + handle.GetDeviceName();
auto search = checked_kdbs.find({program_file, compile_options});
if(search !=
checked_kdbs
.end()) // we have reported this object before, no need to check again
continue;
EXPECT_TRUE(
miopen::CheckKDBObjects(kdb_file_path, program_file, compile_options))
<< "KDB entry not found for fdb-key:" << kinder.first
<< " Solver: " << id.ToString() << " filename:" << program_file
<< " compile_args:" << compile_options;
checked_kdbs.emplace(KDBKey{program_file, compile_options});
BuildKernel(kern.kernel_file, kern.comp_options, handle);
}
}
}
if(kidx % 100 == 0)
std::cout << "Lines of find db completed:" << counter << std::endl;
counter.fetch_add(1, std::memory_order_relaxed);
}
}
TEST(CPU_DBSync_NONE, DISABLED_DynamicFDBSync)
{
fs::path fdb_file_path, pdb_file_path, kdb_file_path;
auto& handle = get_handle();
SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, handle);
miopen::CheckKDBObjects(kdb_file_path, "", "");
const auto& find_db =
miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::FindDb, fdb_file_path.string(), true);
// assert that find_db.cache is not empty, since that indicates the file was not readable
ASSERT_TRUE(!find_db.GetCacheMap().empty()) << "Find DB does not have any entries";
auto _ctx = miopen::ExecutionContext{};
_ctx.SetStream(&handle);
// Convert the map to a vector
std::vector<std::pair<std::string, miopen::ReadonlyRamDb::CacheItem>> fdb_data;
const auto& find_db_map = find_db.GetCacheMap();
fdb_data.resize(find_db_map.size());
std::copy(find_db_map.begin(), find_db_map.end(), fdb_data.begin());
std::atomic<size_t> counter = 0;
const int total_threads = std::min(static_cast<int>(std::thread::hardware_concurrency()), 32);
std::vector<std::thread> agents;
agents.reserve(total_threads);
for(auto idx = 0; idx < total_threads; ++idx)
{
agents.emplace_back(CheckDynamicFDBEntry,
idx,
total_threads,
std::cref(fdb_data),
std::cref(_ctx),
std::ref(counter));
}
for(auto idx = 0; idx < total_threads; ++idx)
{
agents.at(idx).join();
}
ASSERT_TRUE(counter == fdb_data.size())
<< "Multi-threading error, work done is not equal to total work";
}
void CheckFDBEntry(size_t thread_index,
size_t total_threads,
std::vector<FDBLine>& data,
miopen::RamDb& find_db_rw,
miopen::RamDb& perf_db_rw,
const miopen::ExecutionContext& _ctx,
std::atomic<size_t>& counter)
{
fs::path fdb_file_path, pdb_file_path, kdb_file_path;
SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, _ctx.GetStream());
std::unordered_set<KDBKey> checked_kdbs;
const auto data_size = data.size();
auto failures = 0;
for(auto kidx = thread_index; kidx < data_size; kidx += total_threads)
{
const auto& kinder = data.at(kidx);
auto ctx = _ctx;
miopen::conv::ProblemDescription problem;
miopen::ParseProblemKey(kinder.first, problem);
problem.SetupFloats(ctx); // TODO: Check if this is necessary
problem.SetupComputeType(ctx);
std::stringstream ss;
problem.Serialize(ss);
// moment of truth
EXPECT_TRUE(ss.str() == kinder.first)
<< '[' << (++failures) << "] " //
<< "Failed to parse FDB key:" << kidx << ":Parsed Key: " << ss.str();
std::vector<miopen::FDBVal> fdb_vals;
miopen::ParseFDBbVal(kinder.second.content, fdb_vals);
std::unordered_map<std::string, std::string> pdb_vals;
std::string pdb_select_query;
std::string pdb_key;
miopen::GetPerfDbVals(pdb_file_path, problem, pdb_vals, pdb_key, pdb_select_query);
// This is an opportunity to link up fdb and pdb entries
auto fdb_idx = 0; // check kdb only for the fastest kernel
for(const auto& val : fdb_vals)
{
miopen::solver::Id id{val.solver_id};
EXPECT_TRUE(id.IsValid())
<< '[' << (++failures) << "] " //
<< "Solver " << id.Value() << "/" << id.ToString() << ", val.solver_id "
<< val.solver_id << ", val.vals " << val.vals;
#if SKIP_CONVHIPDIRECTFWDFUSED
/// \todo Workaround: solv.IsApplicable() asserts with ConvHipDirectFwdFused
/// on gfx1030. AnySolver instance is empty (nullptr) due to some unknown reason.
if(val.solver_id == "ConvHipDirectFwdFused")
{
MIOPEN_LOG_I("Skipping: val.solver_id " << val.solver_id << ", val.vals "
<< val.vals);
++fdb_idx;
continue;
}
#endif
miopen::solver::ConvSolution sol;
auto db = miopen::GetDb(ctx);
const auto solv = id.GetSolver();
// Skip MLIR
if(miopen::StartsWith(id.ToString(), "ConvMlir"))
{
MIOPEN_LOG_I("Skipping MLIR solver");
++fdb_idx;
continue;
}
if(env::enabled(MIOPEN_DBSYNC_CLEAN) && not solv.IsApplicable(ctx, problem))
{
MIOPEN_LOG_W("Inapplicable solver found fdb-key:"
<< kinder.first << ", pdb-key:" << pdb_key << ", solver:"
<< val.solver_id << ", Removing entry from fdb, pdb and updb");
find_db_rw.Remove(kinder.first, id.ToString());
perf_db_rw.Remove(pdb_key, id.ToString()); // remove from system pdb
db.Remove(problem, id.ToString()); // remove from user pdb
MIOPEN_LOG_W("Removal complete for fdb-key:" << kinder.first
<< ", pdb-key:" << pdb_key
<< ", solver:" << val.solver_id);
continue;
}
else
{
EXPECT_TRUE(solv.IsApplicable(ctx, problem)) //
<< '[' << (++failures) << "] " //
<< "Solver is not applicable fdb-key:" << kinder.first
<< " Solver: " << id.ToString();
}
const auto pdb_entry_exists = pdb_vals.find(val.solver_id) != pdb_vals.end();
if(solv.IsTunable())
{
if(env::enabled(MIOPEN_DBSYNC_CLEAN) && not pdb_entry_exists)
{
MIOPEN_LOG_W("PDB entry does not exist for tunable fdb-key:"
<< kinder.first << ": solver" << val.solver_id
<< ", Removing entry from fdb");
find_db_rw.Remove(kinder.first, id.ToString());
MIOPEN_LOG_W("Removal Complete fdb-key:" << kinder.first << ": solver"
<< val.solver_id);
continue;
}
else
{
EXPECT_TRUE(SKIP_PDB_TESTING || pdb_entry_exists)
<< '[' << (++failures) << "] " //
<< "PDB entry does not exist for tunable fdb-key:" << kinder.first
<< ": solver" << val.solver_id << " pdb-select-query: " << pdb_select_query;
}
std::string perf_cfg = "";
if(!SKIP_PDB_TESTING && pdb_entry_exists)
{
perf_cfg = pdb_vals.at(val.solver_id);
bool res = solv.TestPerfCfgParams(ctx, problem, perf_cfg);
if(env::enabled(MIOPEN_DBSYNC_CLEAN) && not res)
{
MIOPEN_LOG_W("Invalid perf config found fdb-key:"
<< kinder.first << ", pdb-key:" << pdb_key << ", solver:"
<< val.solver_id << ", Removing entry from fdb, pdb and updb");
find_db_rw.Remove(kinder.first, id.ToString());
perf_db_rw.Remove(pdb_key, id.ToString()); // remove from system pdb
db.Remove(problem, id.ToString()); // remove from user pdb
MIOPEN_LOG_W("Removal complete for fdb-key:"
<< kinder.first << ", pdb-key:" << pdb_key
<< ", solver:" << val.solver_id);
continue;
}
else
{
EXPECT_TRUE(res) << '[' << (++failures) << "] " //
<< "Invalid perf config found fdb-key:" << kinder.first
<< ", Solver: " << val.solver_id << ":" << perf_cfg
<< " pdb-select-query: " << pdb_select_query;
}
// we can verify the pdb entry by passing in an empty string and then comparing
// the received solution with the one below or having the find_solution pass out
// the serialized string
sol = solv.FindSolution(ctx, problem, db, {}, perf_cfg);
}
else
{
sol = solv.FindSolution(ctx, problem, db, {}, "");
perf_cfg = " Not Found (Using Default)";
}
// TODO Generate the Select query for pdb
EXPECT_TRUE(sol.Succeeded())
<< '[' << (++failures) << "] " //
<< "Invalid solution fdb-key:" << kinder.first << " Solver: " << id.ToString()
<< " perf config:" << perf_cfg;
if(!SKIP_KDB_TESTING && fdb_idx == 0)
{
for(const auto& kern : sol.construction_params)
{
bool found = false;
std::string compile_options = kern.comp_options;
auto program_file = miopen::make_object_file_name(kern.kernel_file);
if(kern.kernel_file.extension() != ".mlir")
{
auto& handle = ctx.GetStream();
compile_options += " -mcpu=" + handle.GetDeviceName();
}
auto search = checked_kdbs.find({program_file, compile_options});
bool reported_already = search != checked_kdbs.end();
if(!reported_already) // we have reported this object before, no need to
// check again
{
found = miopen::CheckKDBObjects(
kdb_file_path, program_file, compile_options);
checked_kdbs.emplace(KDBKey{program_file, compile_options});
}
else
found = checked_kdbs.count(KDBKey{program_file, compile_options}) > 0;
if(!found)
EXPECT_TRUE(found)
<< '[' << (++failures) << "] " //
<< "KDB entry not found for fdb-key:" << kinder.first
<< ", Solver: " << id.ToString() << " perf config:" << perf_cfg
<< " filename: " << program_file << " compile args: "
<< compile_options; // for fdb key, solver id, solver pdb entry and
// kdb file and args
if(!reported_already)
BuildKernel(kern.kernel_file, kern.comp_options, ctx.GetStream());
}
}
}
else
EXPECT_TRUE(!pdb_entry_exists)
<< '[' << (++failures) << "] " //
<< "Non-Tunable solver found in PDB" << solv.GetSolverDbId();
++fdb_idx;
}
if(kidx % 100 == 0)
std::cout << "Lines of find db completed:" << kidx << std::endl;
counter.fetch_add(1, std::memory_order_relaxed);
}
}
namespace miopen {
struct TestHandle : Handle
{
TestHandle(size_t _num_cu) : Handle(), num_cu(_num_cu) {}
// Probably, according to the idea of the author of this test, the number of CUs should have been
// substituted with the value passed to the constructor (which in fact did not happen). After
// https://github.com/ROCm/MIOpen/pull/3175, the method became virtual, the substitution actually
// happened, and the test broke. I disabled that part (since it doesn't work as intended anyway) to
// keep its behavior the same.
#if 1
std::size_t GetMaxComputeUnits() const override
{
if(num_cu == 0)
return Handle::GetMaxComputeUnits();
return num_cu;
}
#endif
size_t num_cu = 0;
};
} // namespace miopen
static inline miopen::TestHandle& get_test_handle(size_t num_cu)
{
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
static miopen::TestHandle h{num_cu};
static const std::thread::id id = std::this_thread::get_id();
if(std::this_thread::get_id() != id)
{
std::cout << "Cannot use handle across multiple threads\n";
std::abort();
}
return h;
}
void StaticFDBSync(const std::string& arch, const size_t num_cu)
{
fs::path fdb_file_path, pdb_file_path, kdb_file_path;
auto& handle = get_test_handle(num_cu);
if(handle.GetDeviceName() != arch)
GTEST_SKIP();
handle.num_cu = num_cu;
SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, handle);
std::cout << "Handle CU count: " << handle.GetMaxComputeUnits()
<< " Parameter Value: " << num_cu << std::endl;
std::cout << "FDB: " << fdb_file_path << ", PDB: " << pdb_file_path
<< ", KDB: " << kdb_file_path << std::endl;
#if !SKIP_KDB_TESTING
// Warmup the kdb cache
miopen::CheckKDBObjects(kdb_file_path, "", "");
#endif
const auto& find_db =
miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::FindDb, fdb_file_path.string(), true);
auto& find_db_rw =
miopen::RamDb::GetCached(miopen::DbKinds::FindDb, fdb_file_path.string(), false);
// Ensure that find_db.cache is not empty, since that indicates the file was not readable
EXPECT_TRUE(!find_db.GetCacheMap().empty())
<< "Find DB does not have any entries; the file may not be readable.";
const auto& perf_db =
miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::PerfDb, pdb_file_path.string(), true);
auto& perf_db_rw =
miopen::RamDb::GetCached(miopen::DbKinds::PerfDb, pdb_file_path.string(), false);
// Ensure that perf_db.cache is not empty, since that indicates the file was not readable
EXPECT_TRUE(!perf_db.GetCacheMap().empty())
<< "Perf DB does not have any entries; the file may not be readable.";
ASSERT_TRUE(!find_db.GetCacheMap().empty() && !perf_db.GetCacheMap().empty())
<< "Aborting test due to empty/unreadable db(s).";
// Convert the map to a vector
std::vector<FDBLine> fdb_data;
const auto& find_db_map = find_db.GetCacheMap();
fdb_data.resize(find_db_map.size());
std::copy(find_db_map.begin(), find_db_map.end(), fdb_data.begin());
auto _ctx = miopen::ExecutionContext{};
_ctx.SetStream(&handle);
std::atomic<size_t> counter = 0;
const int total_threads =
std::min(std::thread::hardware_concurrency(), static_cast<unsigned int>(32));
std::vector<std::thread> agents;
agents.reserve(total_threads);
for(auto idx = 0; idx < total_threads; ++idx)
agents.emplace_back(CheckFDBEntry,
idx,
total_threads,
std::ref(fdb_data),
std::ref(find_db_rw),
std::ref(perf_db_rw),
std::ref(_ctx),
std::ref(counter));
for(auto idx = 0; idx < total_threads; ++idx)
agents.at(idx).join();
EXPECT_TRUE(counter == fdb_data.size())
<< "Multi-threading error, work done is not equal to total work" << counter << " : "
<< fdb_data.size();
}
struct CPU_DBSync_NONE : testing::TestWithParam<std::pair<std::string, size_t>>
{
};
TEST_P(CPU_DBSync_NONE, StaticFDBSync)
{
#if defined(__SANITIZE_ADDRESS__) || (defined(__has_feature) && __has_feature(address_sanitizer))
// Skip database sync tests when AddressSanitizer is enabled as the database
// file naming may not match the expected xnack configuration.
GTEST_SKIP();
#else
std::string arch;
size_t num_cu;
std::tie(arch, num_cu) = GetParam();
StaticFDBSync(arch, num_cu);
#endif
}
INSTANTIATE_TEST_SUITE_P(Smoke,
CPU_DBSync_NONE,
testing::Values(std::make_pair("gfx908", 120),
std::make_pair("gfx90a", 104),
std::make_pair("gfx90a", 110),
std::make_pair("gfx942", 304),
std::make_pair("gfx950", 256),
std::make_pair("gfx1030", 36),
std::make_pair("gfx1100", 0),
std::make_pair("gfx1102", 0),
std::make_pair("gfx1201", 0)));