// Copyright (c) Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #ifndef GUARD_MIOPEN_TEST_DRIVER_HPP #define GUARD_MIOPEN_TEST_DRIVER_HPP #include "args.hpp" #include "get_handle.hpp" #include "network_data.hpp" #include "serialize.hpp" #include "miopen/stringutils.hpp" #include "tensor_holder.hpp" #include "test.hpp" #include "verify.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace env = miopen::env; template constexpr std::is_same is_same(const T&) { return {}; } struct tensor_elem_gen_checkboard_sign { template double operator()(Ts... Xs) const { std::array dims = {{Xs...}}; return std::accumulate(dims.begin(), dims.end(), true, [](int init, uint64_t x) -> int { return init != (x % 2); }) ? 1 : -1; } }; template auto is_const_cpu(const V& v, Ts&&... xs) -> decltype(v.cpu(xs...), std::true_type{}) { return {}; } template auto is_const_cpu(V& v, Ts&&... xs) -> decltype(v.cpu(xs...), std::false_type{}) { return {}; } // Run cpu in parallel if it can be ran as const template auto cpu_async(const V& v, Ts&&... xs) -> std::future { return detach_async([&] { return v.cpu(xs...); }); } template auto cpu_async(V& v, Ts&&... xs) -> std::future { return std::async(std::launch::deferred, [&] { return v.cpu(xs...); }); } MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_VERIFY_CACHE_PATH) struct test_driver { test_driver() = default; test_driver(const test_driver&) = delete; test_driver& operator=(const test_driver&) = delete; struct argument { std::function)> write_value; std::function read_value; std::vector> post_write_actions; std::vector)>> data_sources; std::string type; std::string name; // Function may refer to the argument by reference so this needs to be noncopyable argument() = default; argument(const argument&) = delete; argument& operator=(const argument&) = delete; void post_write() { for(const auto& pw : post_write_actions) { pw(); } } void write(std::vector c) { write_value(c); post_write(); } template void add_source(Source src, T& x) { data_sources.push_back([=, this, &x](std::function callback) { for(auto y : src()) // NOLINT { x = T(y); post_write(); callback(); } }); } }; static std::string compute_cache_path() { auto s = env::value(MIOPEN_VERIFY_CACHE_PATH); if(s.empty()) s = "~/.cache/miopen/tests"; return s; } std::string program_name; std::deque arguments; std::unordered_map argument_index; int cache_version = 1; std::string cache_path = compute_cache_path(); miopenDataType_t type = miopenFloat; bool full_set = false; int limit_set = 2; int dataset_id = 0; bool verbose = false; double tolerance = 80; bool time = false; int time_iter = 1; int warmup_iter = 0; int batch_factor = 0; bool no_validate = false; int repeat = 1; bool rethrow = false; bool disabled_cache = false; bool dry_run = false; int config_iter_start = 0; int config_iter_end = -1; int iteration = 0; argument& get_argument(const std::string& s) { assert(arguments.at(argument_index.at(s)).name == s); return arguments.at(argument_index.at(s)); } bool has_argument(const std::string& arg) const { return argument_index.contains(arg); } template void parse(Visitor v) { v(full_set, {"--all"}, "Run all tests"); v(limit_set, {"--limit"}, "Limits the number of generated test elements"); v(dataset_id, {"--dataset"}, "Identifies the data set used for generation of tests (default=0)"); v(verbose, {"--verbose", "-v"}, "Run verbose mode"); v(tolerance, {"--tolerance", "-t"}, "Set test tolerance"); v(time, {"--time"}, "Time the kernel on GPU"); v(time_iter, {"--time-iter"}, "The number of iterations to average for timing the kernel"); v(warmup_iter, {"--warmup-iter"}, "The number of warmup iterations for timing the kernel"); v(batch_factor, {"--batch-factor", "-n"}, "Set batch factor"); v(no_validate, {"--disable-validation"}, "Disable cpu validation, so only gpu version is ran"); v(repeat, {"--repeat"}, "Repeat the tests"); v(rethrow, {"--rethrow"}, "Rethrow any exceptions found during verify"); v(cache_path, {"--verification-cache", "-C"}, "Path to verification cache"); v(disabled_cache, {"--disable-verification-cache"}, "Disable verification cache"); v(dry_run, {"--dry-run"}, "Dry run. Does not run the test, just prints the command."); v(config_iter_start, {"--config-iter-start", "-i"}, "index of config at which to start a test." "Can be used to restart a test after a failing config."); v(config_iter_end, {"--config-iter-end", "-e"}, "index of config at which to end a test (exclusive)."); } struct per_arg { template void operator()(T& x, argument& a, Action action) const { action(x, a); } }; template void add(T& x, std::string name, Fs... fs) { argument_index.insert(std::make_pair(name, arguments.size())); arguments.emplace_back(); argument& arg = arguments.back(); arg.name = name; arg.type = miopen::get_type_name(); arg.write_value = [&](std::vector params) { args::write_value{}(x, params); }; arg.read_value = [&] { return args::read_value{}(x); }; miopen::each_args(std::bind(per_arg{}, std::ref(x), std::ref(arg), std::placeholders::_1), fs...); // assert(get_argument(name).name == name); } void show_help() { std::cout << "Driver arguments: " << std::endl; this->parse([&](const auto& var, std::initializer_list x, std::string help) { std::cout << std::endl; std::string prefix = " "; for(const std::string& a : x) { std::cout << prefix; std::cout << a; prefix = ", "; } if(not is_same(var)) std::cout << " [" << miopen::get_type_name(var) << "]"; std::cout << std::endl; std::cout << " " << help << std::endl; }); std::cout << std::endl; std::cout << "Test inputs: " << std::endl; for(auto&& arg : this->arguments) { std::cout << " --" << arg.name; if(not arg.type.empty()) std::cout << " [" << arg.type << "]"; std::cout << std::endl; } std::cout << std::endl; } std::string get_command_args() { std::stringstream ss; switch(this->type) { case miopenHalf: ss << "--half "; break; case miopenBFloat16: ss << "--bfloat16 "; break; case miopenInt8: ss << "--int8 "; break; case miopenInt32: ss << "--int32 "; break; case miopenInt64: ss << "--int64 "; break; case miopenFloat: ss << "--float "; break; case miopenDouble: ss << "--double "; break; case miopenFloat8_fnuz: ss << "--float8"; break; case miopenBFloat8_fnuz: ss << "--bfloat8"; break; } for(auto&& arg : this->arguments) { std::string value = arg.read_value(); if(not value.empty()) { ss << "--" << arg.name << " "; if(value != arg.name) ss << value << " "; } } return ss.str(); } std::vector get_config() { std::vector ret; switch(this->type) { case miopenHalf: ret.emplace_back("--half"); break; case miopenBFloat16: ret.emplace_back("--bf16"); break; case miopenInt8: ret.emplace_back("--int8"); break; case miopenInt32: ret.emplace_back("--int32"); break; case miopenInt64: ret.emplace_back("--int64"); break; case miopenFloat: ret.emplace_back("--float"); break; case miopenDouble: ret.emplace_back("--double"); break; case miopenFloat8_fnuz: ret.emplace_back("--float8"); break; case miopenBFloat8_fnuz: ret.emplace_back("--bfloat8"); break; } for(auto&& arg : this->arguments) { std::string value = arg.read_value(); std::vector value_vector = miopen::SplitSpaceSeparated(value); if(not value.empty()) { ret.emplace_back("--" + arg.name); if(value != arg.name) ret.insert(ret.end(), value_vector.begin(), value_vector.end()); } } return ret; } void show_command() { std::cout << this->program_name << " "; std::cout << get_command_args() << std::endl; } template struct generate_tensor_t { std::function()> get_data; G tensor_elem_gen; template void operator()(T& x, argument& arg) const { arg.add_source(get_data, x); G g = tensor_elem_gen; arg.post_write_actions.push_back([&x, g] { tensor_generate{}(x, g); }); } }; template generate_tensor_t generate_tensor(std::set dims, X single, G g) { return {[=, this]() -> std::set { if(full_set) return dims; else return {single}; }, g}; } template generate_tensor_t, G> generate_tensor(std::set> dims, std::initializer_list single, G g) { return generate_tensor, G>(dims, single, g); } template auto lazy_generate_tensor(F f, G g) -> generate_tensor_t, G> { return {[=, this]() -> decltype(f()) { if(full_set) return f(); else return {*f().begin()}; }, g}; } template generate_tensor_t lazy_generate_tensor(F f, X single, G g) { return {[=, this]() -> std::set { if(full_set) return f(); else return {single}; }, g}; } template generate_tensor_t, G> lazy_generate_tensor(F f, std::initializer_list single, G g) { return lazy_generate_tensor, G>(f, single, g); } template generate_tensor_t, G> get_tensor(F gen_shapes, G gen_value) { return lazy_generate_tensor([=, this] { return gen_shapes(batch_factor); }, gen_value); } template generate_tensor_t, G> get_bn_spatial_input_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor([=, this] { return get_bn_spatial_inputs(batch_factor); }, {4, 64, 28, 28}, tensor_elem_gen); } template generate_tensor_t, G> get_bn_peract_input_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor([=, this] { return get_bn_peract_inputs(batch_factor); }, {16, 32, 8, 8}, tensor_elem_gen); } template generate_tensor_t, G> get_input_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor( [=, this] { return get_inputs(batch_factor); }, {16, 32, 8, 8}, tensor_elem_gen); } template generate_tensor_t, G> get_3d_bn_spatial_input_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor([=, this] { return get_3d_bn_spatial_inputs(batch_factor); }, {16, 32, 8, 8, 8}, tensor_elem_gen); } template generate_tensor_t, G> get_3d_bn_peract_input_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor([=, this] { return get_3d_bn_peract_inputs(batch_factor); }, {16, 32, 8, 8, 8}, tensor_elem_gen); } template generate_tensor_t, G> get_weights_tensor(G tensor_elem_gen = tensor_elem_gen_integer{}) { return lazy_generate_tensor( [=, this] { return get_weights(batch_factor); }, {64, 32, 5, 5}, tensor_elem_gen); } template struct generate_data_t { std::function get_data; template void operator()(T& x, argument& arg) const { arg.add_source(get_data, x); } // This is necessary to reuse lambdas provided by generate_data*() // in the lambdas generated by the generate_multi_data*(). template std::vector operator()(const T&) const { return get_data(); } }; template generate_data_t> generate_data(std::vector dims, T single) { return {[=, this]() -> std::vector { if(full_set) return dims; else return {single}; }}; } template generate_data_t> generate_data_limited(std::vector dims, int limit_multiplier, T single) { return {[=, this]() -> std::vector { if(full_set) { if(limit_set > 0) { auto endpoint = std::min(static_cast(dims.size()), limit_set * limit_multiplier); std::vector subvec(dims.cbegin(), dims.cbegin() + endpoint); return subvec; } else return dims; } else { return {single}; } }}; } template generate_data_t> generate_data(std::initializer_list dims) { return generate_data(std::vector(dims)); } template generate_data_t>> generate_data(std::initializer_list> dims) { return generate_data(std::vector>(dims.begin(), dims.end())); } template generate_data_t> generate_data(std::vector dims) { return {[=, this]() -> std::vector { if(full_set) return dims; else return {dims.front()}; }}; } template generate_data_t> generate_multi_data(std::vector> multi_dims) { return {[=, this]() -> std::vector { return generate_data(multi_dims.at(dataset_id))(T{}); }}; } template generate_data_t> generate_data_limited(std::vector dims, int limit_multiplier) { return {[=, this]() -> std::vector { if(full_set) { if(limit_set > 0) { auto endpoint = std::min(static_cast(dims.size()), limit_set * limit_multiplier); std::vector subvec(dims.cbegin(), dims.cbegin() + endpoint); return subvec; } else { return dims; } } else { return {dims.front()}; } }}; } template generate_data_t> generate_multi_data_limited(std::vector> multi_dims, int limit_multiplier) { return {[=, this]() -> std::vector { return generate_data_limited(multi_dims.at(dataset_id), limit_multiplier)(T{}); }}; } template auto lazy_generate_data(F f, T single) -> generate_data_t { return {[=, this]() -> decltype(f()) { if(full_set) return f(); else return {single}; }}; } template auto lazy_generate_data(F f) -> generate_data_t { return {[=, this]() -> decltype(f()) { if(full_set) return f(); else return {f().front()}; }}; } template generate_data_t> generate_single(T single) { return {[=, this]() -> std::vector { return {single}; }}; } template struct set_value_t { X value; template void operator()(T& x, argument& arg) const { auto y = value; arg.type = ""; arg.write_value = [&x, y](std::vector as) { if(not as.empty()) throw std::runtime_error("Argument should not have any additional parameters"); x = y; }; arg.read_value = [&x, &arg, y]() -> std::string { if(x == y) return arg.name; else return ""; }; } }; template set_value_t set_value(T x) { return {x}; } set_value_t flag() { return set_value(true); } auto verify_reporter() { return [=, this](bool pass, std::vector error, const auto& out_cpu, const auto& out_gpu, auto fail) { if(not pass or verbose) { if(not error.empty() or not pass) { if(not verbose) show_command(); if(not error.empty()) std::cout << (pass ? "error: " : "FAILED: ") << error.front() << std::endl; else std::cout << "FAILED: " << std::endl; if(not verbose) { std::cout << "Iteration: " << this->iteration << std::endl; fail(-1); } } auto mxdiff = miopen::max_diff(out_cpu, out_gpu); std::cout << "Max diff: " << mxdiff << std::endl; // auto max_idx = miopen::mismatch_diff(out_cpu, out_gpu, mxdiff); // std::cout << "Max diff at " << max_idx << ": " << out_cpu[max_idx] << // " != // " << out_gpu[max_idx] << std::endl; if(miopen::range_zero(out_cpu)) std::cout << "Cpu data is all zeros" << std::endl; if(miopen::range_zero(out_gpu)) std::cout << "Gpu data is all zeros" << std::endl; auto idx = miopen::mismatch_idx(out_cpu, out_gpu, miopen::float_equal); if(idx < miopen::range_distance(out_cpu)) { std::cout << "Mismatch at " << idx << ": " << out_cpu[idx] << " != " << out_gpu[idx] << std::endl; } auto cpu_nan_idx = find_idx(out_cpu, miopen::not_finite); if(cpu_nan_idx >= 0) { std::cout << "Non finite number found in cpu at " << cpu_nan_idx << ": " << out_cpu[cpu_nan_idx] << std::endl; } auto gpu_nan_idx = find_idx(out_gpu, miopen::not_finite); if(gpu_nan_idx >= 0) { std::cout << "Non finite number found in gpu at " << gpu_nan_idx << ": " << out_gpu[gpu_nan_idx] << std::endl; } } else if(miopen::range_zero(out_cpu) and miopen::range_zero(out_gpu) and (miopen::range_distance(out_cpu) != 0)) { show_command(); std::cout << "Warning: Both CPU and GPU data is all zero" << std::endl; fail(-1); } return true; }; } template bool compare_and_report( const CpuRange& out_cpu, const GpuRange& out_gpu, Compare compare, Report report, Fail fail) { std::vector error; bool pass = compare(error, out_cpu, out_gpu); return report(pass, error, out_cpu, out_gpu, fail); } template bool compare_and_report(const std::tuple& out_cpu, const std::tuple& out_gpu, Compare compare, Report report, Fail fail) { static_assert(sizeof...(CpuRanges) == sizeof...(GpuRanges), "Cpu and gpu mismatch"); return miopen::sequence([&](auto... is) { bool continue_ = true; miopen::each_args( [&](auto i) { // cppcheck-suppress knownConditionTrueFalse if(continue_) { continue_ = this->compare_and_report( std::get(out_cpu), std::get(out_gpu), compare, report, [&](int) { return fail(i); }); } }, is...); return continue_; })(std::integral_constant{}); } bool is_cache_disabled() const { if(disabled_cache) return true; auto p = miopen::ExpandUser(cache_path) / ".disabled"; return miopen::fs::exists(p); } template auto run_cpu(bool retry, bool& miss, V& v, Ts&&... xs) -> std::future { using result_type = decltype(v.cpu(xs...)); if(is_cache_disabled() or not is_const_cpu(v, xs...)) return cpu_async(v, xs...); auto key = miopen::get_type_name() + "-" + miopen::md5(get_command_args()); auto p = miopen::ExpandUser(cache_path) / std::to_string(cache_version); if(!miopen::fs::exists(p)) miopen::fs::create_directories(p); auto f = p / key; if(miopen::fs::exists(f) and not retry) { miss = false; return miopen::detach_async([=] { result_type result; load(f.string(), result); return std::move(result); }); } else { miss = true; return miopen::then(cpu_async(v, xs...), [=](auto data) { save(f.string(), data); return data; }); } } template void adjust_parameters_impl(miopen::rank<0>, V&&) { } /// Winograd algorithm has worse precision than Direct and Gemm. /// Winograd-specific precision loss is roughly 2+2 bits. /// Let's adjust tolerance (only for FP32 WrW for now). template auto adjust_parameters_impl(miopen::rank<1>, V&& v) -> decltype(v.stats, v.is_conv_wrw_f32, void()) { if(v.is_conv_wrw_f32 && v.stats->algorithm == miopenConvolutionAlgoWinograd) tolerance *= 16.0; } template auto adjust_parameters(V&& v) -> decltype(adjust_parameters_impl(miopen::rank<1>{}, v)) { return adjust_parameters_impl(miopen::rank<1>{}, v); } template auto verify_impl(F&& f, V&& v, Ts&&... xs) -> decltype(std::make_pair(v.cpu(xs...), v.gpu(xs...))) { decltype(v.cpu(xs...)) cpu; decltype(v.gpu(xs...)) gpu; if(verbose or time) show_command(); try { auto&& h = get_handle(); // Compute cpu std::future cpuf; bool cache_miss = true; if(not no_validate) { cpuf = run_cpu(false, cache_miss, v, xs...); } // Compute gpu if(time) { for(size_t i = 0; i < warmup_iter; ++i) { v.gpu(xs...); } h.EnableProfiling(); h.ResetKernelTime(); } gpu = v.gpu(xs...); if(time) { float total_time = h.GetKernelTime(); for(size_t i = 1; i < time_iter; ++i) { h.ResetKernelTime(); v.gpu(xs...); total_time += h.GetKernelTime(); } std::cout << "Kernel time: " << (total_time / time_iter) << " ms" << std::endl; h.EnableProfiling(false); } adjust_parameters(v); // Validate if(!no_validate) { cpu = cpuf.get(); auto report = this->verify_reporter(); bool retry = true; if(not cache_miss) { retry = false; auto report_retry = [&](bool pass, std::vector error, const auto& out_cpu, const auto& out_gpu, auto fail) { if(not pass) { retry = true; return false; } return report(pass, error, out_cpu, out_gpu, fail); }; compare_and_report( cpu, gpu, f, report_retry, [&](int mode) { v.fail(mode, xs...); }); // cppcheck-suppress knownConditionTrueFalse if(retry) { std::cout << "Warning: verify cache failed, rerunning cpu." << std::endl; cpu = run_cpu(retry, cache_miss, v, xs...).get(); } } if(retry) compare_and_report(cpu, gpu, f, report, [&](int mode) { v.fail(mode, xs...); }); } if(verbose or time) v.fail(std::integral_constant{}, xs...); } catch(const std::exception& ex) { show_command(); std::cout << "FAILED: " << ex.what() << std::endl; v.fail(-1, xs...); if(rethrow) throw; } catch(...) { show_command(); std::cout << "FAILED with unknown exception" << std::endl; v.fail(-1, xs...); if(rethrow) throw; } if(no_validate) { return std::make_pair(gpu, gpu); } else { return std::make_pair(cpu, gpu); } } template auto verify_eps(V&& v, Ts&&... xs) -> decltype(std::make_pair(v.cpu(xs...), v.gpu(xs...))) { return verify_impl( [&](std::vector& error, auto&& cpu, auto&& gpu) { CHECK(miopen::range_distance(cpu) == miopen::range_distance(gpu)); double threshold = v.epsilon() * tolerance; error = {miopen::rms_range(cpu, gpu)}; return error.front() <= threshold; }, v, xs...); } template auto verify(V&& v, Ts&&... xs) -> decltype(std::make_pair(v.cpu(xs...), v.gpu(xs...))) { return verify_impl( [&](std::vector& error, auto&& cpu, auto&& gpu) { CHECK(miopen::range_distance(cpu) == miopen::range_distance(gpu)); using value_type = miopen::range_value; double threshold = std::numeric_limits::epsilon() * tolerance; error = {miopen::rms_range(cpu, gpu)}; return error.front() <= threshold; }, v, xs...); } template auto verify_equals(V&& v, Ts&&... xs) -> decltype(std::make_pair(v.cpu(xs...), v.gpu(xs...))) { return verify_impl( [&](auto&, auto&& cpu, auto&& gpu) { auto idx = miopen::mismatch_idx(cpu, gpu, miopen::float_equal); return idx >= miopen::range_distance(cpu); }, v, xs...); } template void base_run() { if(this->iteration >= this->config_iter_start && (this->config_iter_end < 0 || this->iteration < this->config_iter_end)) { if(this->time && !this->verbose) { std::cout << "Iteration: " << this->iteration << std::endl; } if(this->dry_run) { std::cout << "Iteration: " << this->iteration << std::endl; show_command(); } else { prng::reset_seed(); static_cast(this)->run(); prng::reset_seed(); } } this->iteration++; } }; template void run_data(Iterator start, Iterator last, Action a) { if(start == last) { a(); return; } auto&& sources = (*start)->data_sources; if(sources.empty()) { run_data(std::next(start), last, a); } else { for(auto&& src : sources) { src([=] { run_data(std::next(start), last, a); }); } } } struct keyword_set { std::set* value; keyword_set(std::set& x) : value(&x) {} template void operator()(T&&, std::initializer_list x, std::string) const { value->insert(x); } }; struct parser { args::string_map* m; parser(args::string_map& x) : m(&x) {} template void operator()(T& x, std::initializer_list keywords, std::string) const { for(auto&& keyword : keywords) { if(m->count(keyword) > 0) { try { args::write_value{}(x, (*m)[keyword]); return; } catch(...) { std::cerr << "Invalid argument: " << keyword << std::endl; throw; } } } } void operator()(bool& x, std::initializer_list keywords, std::string) const { for(auto&& keyword : keywords) { if(m->count(keyword) > 0) { x = true; return; } } } }; template void check_unparsed_args(Driver& d, std::unordered_map>& arg_map, std::set& keywords) { for(auto&& p : arg_map) { if(p.first.empty()) { std::cerr << "Unused arguments: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; std::abort(); } else if(!keywords.contains(p.first)) { assert(p.first.length() > 2); auto name = p.first.substr(2); try { auto&& arg = d.get_argument(name); arg.write(p.second); } catch(const std::exception& ex) { std::cerr << "Invalid argument: " << name << std::endl; std::cerr << "With parameters: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; std::cerr << ex.what() << std::endl; std::abort(); } catch(...) { std::cerr << "Invalid argument: " << name << std::endl; std::cerr << "With parameters: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; throw; } } } } #if(MIOPEN_TEST_DRIVER_MODE == 2) template std::vector get_data_args(Driver& d, std::unordered_map>& arg_map) { // Run data on arguments that are not passed in std::vector data_args; for(auto&& arg : d.arguments) { if(arg_map.count("--" + arg.name) == 0) { data_args.push_back(&arg); } } return data_args; } template void set_driver_datatype(Driver& d, std::unordered_map>& arg_map) { if(arg_map.count("--half") > 0) { d.type = miopenHalf; } else if(arg_map.count("--int8") > 0) { d.type = miopenInt8; } else if(arg_map.count("--double") > 0) { d.type = miopenDouble; } else { d.type = miopenFloat; } } template std::vector> build_configs(Driver& d, std::unordered_map>& arg_map, std::set& keywords) { std::cout << "Building configs..." << std::endl; std::vector> configs; d.parse(parser{arg_map}); check_unparsed_args(d, arg_map, keywords); std::vector data_args = get_data_args(d, arg_map); run_data(data_args.begin(), data_args.end(), [&] { prng::reset_seed(); std::vector config = d.get_config(); configs.push_back(config); prng::reset_seed(); }); std::cout << " done." << std::endl; return configs; } template std::unordered_map> create_arg_map(Driver& d, std::set& keywords, std::vector& args) { d.parse(keyword_set{keywords}); return args::parse(args, [&](std::string x) { return (keywords.count(x) > 0) or ((x.compare(0, 2, "--") == 0) and d.has_argument(x.substr(2))); }); } template void run_config(std::vector& config, std::unordered_map>& arg_map, std::string& program_name, std::set& keywords, int& test_repeat_count) { Driver config_driver{}; config_driver.program_name = program_name; auto config_arg_map = create_arg_map(config_driver, keywords, config); set_driver_datatype(config_driver, config_arg_map); config_driver.parse(parser{config_arg_map}); check_unparsed_args(config_driver, config_arg_map, keywords); std::vector config_data_args = get_data_args(config_driver, config_arg_map); if(arg_map.count("--verbose") > 0) { config_driver.verbose = true; } if(arg_map.count("--disable-verification-cache") > 0) { config_driver.disabled_cache = true; } for(int j = 0; j < test_repeat_count; j++) { run_data(config_data_args.begin(), config_data_args.end(), [&] { prng::reset_seed(); config_driver.run(); prng::reset_seed(); }); } } template void test_drive_impl_2(std::string program_name, std::vector as) { Driver d{}; d.program_name = program_name; std::set keywords{"--help", "-h", "--half", "--float", "--double", "--int8"}; auto arg_map = create_arg_map(d, keywords, as); int test_repeat_count = d.repeat; // Show help if((arg_map.count("-h") > 0) or (arg_map.count("--help") > 0)) { d.show_help(); return; } set_driver_datatype(d, arg_map); std::vector> configs = build_configs(d, arg_map, keywords); size_t config_count = configs.size(); double running_average = 0; // iterate through and run configs for(size_t i = d.config_iter_start; i < config_count; ++i) { std::cout << "Config " << i + 1 << "/" << config_count << std::endl; auto start = std::chrono::high_resolution_clock::now(); // Record start time run_config(configs[i], arg_map, program_name, keywords, test_repeat_count); auto finish = std::chrono::high_resolution_clock::now(); // Record end time std::chrono::duration elapsed = finish - start; if(i == 0) { running_average = elapsed.count(); } else { running_average = [](auto avg_acc, double y, int avg_size) { return (avg_acc - avg_acc / avg_size) + y / avg_size; }(running_average, elapsed.count(), i + 1); // (avg_acc/N-1) * ((N-1)/N) + y/N; } std::cout << "Elapsed time: " << elapsed.count() << " s" << ", " << "Running Average: " << running_average << " s" << std::endl; } } #endif template void test_drive_impl_1(std::string program_name, std::vector as) { Driver d{}; d.program_name = program_name; std::cout << program_name << " "; for(const auto& str : as) std::cout << str << " "; std::cout << std::endl; std::set keywords{ "--help", "-h", "--half", "--float", "--double", "--int8", "--bfloat16"}; d.parse(keyword_set{keywords}); auto arg_map = args::parse(as, [&](std::string x) { return (keywords.contains(x)) or ((x.starts_with("--")) and d.has_argument(x.substr(2))); }); if(arg_map.count("--half") > 0) { d.type = miopenHalf; } else if(arg_map.count("--int8") > 0) { d.type = miopenInt8; } else if(arg_map.count("--bfloat16") > 0) { d.type = miopenBFloat16; } else if(arg_map.count("--double") > 0) { d.type = miopenDouble; } else { d.type = miopenFloat; } // Show help if((arg_map.count("-h") > 0) or (arg_map.count("--help") > 0)) { d.show_help(); return; } d.parse(parser{arg_map}); for(auto&& p : arg_map) { if(p.first.empty()) { std::cerr << "Unused arguments: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; std::abort(); } else if(keywords.count(p.first) == 0) { assert(p.first.length() > 2); auto name = p.first.substr(2); try { auto&& arg = d.get_argument(name); arg.write(p.second); } catch(const std::exception& ex) { std::cerr << "Invalid argument: " << name << std::endl; std::cerr << "With parameters: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; std::cerr << ex.what() << std::endl; std::abort(); } catch(...) { std::cerr << "Invalid argument: " << name << std::endl; std::cerr << "With parameters: " << std::endl; for(auto&& s : p.second) std::cerr << " " << s << std::endl; throw; } } } // Run data on arguments that are not passed in std::vector data_args; for(auto&& arg : d.arguments) { if(arg_map.count("--" + arg.name) == 0) { data_args.push_back(&arg); } } prng::reset_seed(); for(int i = 0; i < d.repeat; i++) { d.iteration = 0; run_data(data_args.begin(), data_args.end(), [&] { d.template base_run(); }); } } template void test_drive_impl(std::string program_name, std::vector as) { #if(MIOPEN_TEST_DRIVER_MODE == 2) std::cout << "MIOPEN_TEST_DRIVER_MODE 2." << std::endl; test_drive_impl_2(program_name, as); #else test_drive_impl_1(program_name, as); #endif } template void test_drive(int argc, const char* argv[], const char* program_name = nullptr) { std::string name(program_name ? program_name : argv[0]); std::vector as(argv + (program_name ? 0 : 1), argv + argc); test_drive_impl(name, std::move(as)); } template