#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program_impl { // A map is used to keep references to modules of the program // all the modules are store in the depth-first order std::list modules; context ctx; std::string target_name; }; program::program() : impl(std::make_unique()) { impl->modules.push_back({"main"}); } program::program(program&&) noexcept = default; program::~program() noexcept = default; // copy constructor program::program(const program& p) { assign(p); } // copy assignment operator program& program::operator=(program p) { std::swap(p.impl, this->impl); return *this; } void program::assign(const program& p) { if(!impl) { impl = std::make_unique(); } else if(!impl->modules.empty()) { impl->modules.clear(); } impl->ctx = p.impl->ctx; impl->target_name = p.impl->target_name; impl->modules = p.impl->modules; // build a map from old ins to new ins // Build a map from old module to new module std::unordered_map mod_map; std::transform(impl->modules.begin(), impl->modules.end(), p.impl->modules.begin(), std::inserter(mod_map, mod_map.begin()), [](auto&& x, auto&& y) { return std::make_pair(&y, &x); }); std::unordered_map ins_map; for(auto&& pp : mod_map) { auto old_ins = iterator_for(*pp.first); auto new_ins = iterator_for(*pp.second); std::transform(old_ins.begin(), old_ins.end(), new_ins.begin(), std::inserter(ins_map, ins_map.begin()), [](auto x, auto y) { return std::make_pair(x, y); }); } // Update all references from all modules for(auto&& mp : impl->modules) { for(auto ins : iterator_for(mp)) instruction::replace_refs(ins, ins_map, mod_map); } } shape program::get_parameter_shape(std::string name) const { const auto* mm = this->get_main_module(); return mm->get_parameter_shape(std::move(name)); } std::vector program::get_parameter_names() const { const auto* mm = this->get_main_module(); return mm->get_parameter_names(); } instruction_ref program::get_parameter(std::string name) const { const auto* mm = this->get_main_module(); return mm->get_parameter(std::move(name)); } std::unordered_map program::get_parameter_shapes() const { const auto* mm = this->get_main_module(); return mm->get_parameter_shapes(); } std::size_t program::size() const { return impl->modules.size(); } std::vector program::get_output_shapes() const { const auto* mm = this->get_main_module(); return mm->get_output_shapes(); } context& program::get_context() const { return impl->ctx; } instruction_ref program::validate() const { const auto* mm = this->get_main_module(); return mm->validate(); } bool program::is_compiled() const { return not this->impl->target_name.empty(); } void program::compile(const target& t, compile_options options) { assert(not this->is_compiled()); this->impl->target_name = t.name(); this->impl->ctx = t.get_context(); if(enabled(MIGRAPHX_TRACE_COMPILE{})) options.trace = tracer{std::cout}; options.trace(*this); options.trace(); auto&& passes = t.get_passes(this->impl->ctx, options); auto* modl = get_main_module(); assert(modl->validate() == modl->end()); run_passes(*modl, passes, options.trace); auto invalid = this->validate(); if(invalid != modl->end()) { auto index = std::distance(modl->begin(), invalid); MIGRAPHX_THROW("Invalid module " + modl->name() + " from compilation at instruction " + std::to_string(index)); } modl->finalize(this->impl->ctx); } void program::finalize() { auto* mm = this->get_main_module(); mm->finalize(this->impl->ctx); } template std::vector generic_eval(const module& p, context& ctx, std::unordered_map params, F trace) { assert(p.validate() == p.end()); std::unordered_map results; results.reserve(p.size() * 2); std::vector values; values.reserve(16); for(auto ins : iterator_for(p)) { const auto& name = ins->name(); if(name == "@literal") { results.emplace(ins, trace(ins, [&] { return ins->get_literal().get_argument(); })); } else if(name == "@param") { results.emplace( ins, trace(ins, [&] { auto param_name = any_cast(ins->get_operator()).parameter; if(not contains(params, param_name)) MIGRAPHX_THROW("Parameter not found: " + param_name); auto param = params[param_name]; if(param.get_shape() != ins->get_shape()) MIGRAPHX_THROW("Incorrect shape {" + to_string(param.get_shape()) + "} for parameter: " + param_name); return param; })); } else if(name == "@outline") { results.emplace(ins, trace(ins, [&] { return argument{ins->get_shape(), nullptr}; })); } else if(name == "@return") { std::vector prog_outputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(prog_outputs), [&](instruction_ref i) { assert(results.find(i) != results.end()); return results[i]; }); return prog_outputs; } else { values.resize(ins->inputs().size()); std::transform( ins->inputs().begin(), ins->inputs().end(), values.begin(), [&](instruction_ref i) { assert(results.find(i) != results.end()); return results[i]; }); results.emplace(ins, trace(ins, [&] { return ins->normalized_operator().compute( ctx, ins->get_shape(), values); })); } assert(results.find(ins) != results.end()); } return {results.at(std::prev(p.end()))}; } template std::vector generic_eval(const program& p, context& ctx, std::unordered_map params, F trace) { const auto* mm = p.get_main_module(); return generic_eval(*mm, ctx, params, trace); } std::vector program::eval(parameter_map params) const { auto& ctx = this->impl->ctx; #ifndef NDEBUG auto sctx = ctx; auto check_context = [&](auto f) { assert(is_shared(ctx, sctx)); auto x = f(); sctx = ctx; return x; }; #else auto check_context = [](auto f) { return f(); }; #endif auto trace_level = value_of(MIGRAPHX_TRACE_EVAL{}); if(trace_level > 0) { return generic_eval(*this, ctx, std::move(params), [&](auto& ins, auto f) { ctx.finish(); std::cout << "Run instruction: "; this->debug_print(ins); auto result = check_context(f); ctx.finish(); if(trace_level > 1 and ins->name().front() != '@' and ins->name() != "load") std::cout << "Ouput: " << result << std::endl; return result; }); } else { return generic_eval( *this, ctx, std::move(params), [&](auto&, auto f) { return check_context(f); }); } } const int program_file_version = 4; value program::to_value() const { value result; result["version"] = program_file_version; result["target"] = this->impl->target_name; if(not this->impl->target_name.empty()) result["context"] = this->impl->ctx.to_value(); value module_vals = value::array{}; std::unordered_map names; for(auto& mod : this->impl->modules) { value mod_val; value nodes; mod_val["name"] = mod.name(); names = mod.print( [&](auto ins, auto ins_names) { value node; node["output"] = ins_names.at(ins); node["name"] = ins->name(); node["shape"] = migraphx::to_value(ins->get_shape()); node["normalized"] = ins->is_normalized(); if(ins->name() == "@literal") node["literal"] = migraphx::to_value(ins->get_literal()); node["operator"] = ins->get_operator().to_value(); std::vector inputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto i) { assert(contains(ins_names, i)); return ins_names.at(i); }); node["inputs"] = inputs; auto module_args = ins->module_inputs(); if(not module_args.empty()) { std::vector module_inputs; std::transform(module_args.begin(), module_args.end(), std::back_inserter(module_inputs), [&](auto mod_ref) { return mod_ref->name(); }); node["module_inputs"] = module_inputs; } nodes.push_back(node); }, names); mod_val["nodes"] = nodes; module_vals.push_back(mod_val); } result["modules"] = module_vals; return result; } static void mod_from_val(module_ref mod, const value& v, std::unordered_map& instructions, const std::unordered_map& map_mods) { const auto* it = std::find_if(v.begin(), v.end(), [&](auto& mv) { return mv.at("name").template to() == mod->name(); }); assert(it != v.end()); const auto& module_val = *it; for(const value& node : module_val.at("nodes")) { instruction_ref output; auto name = node.at("name").to(); auto fields = node.at("operator"); auto normalized = node.at("normalized").to(); if(name == "@param") { output = mod->add_parameter(fields["parameter"].to(), migraphx::from_value(node.at("shape"))); } else if(name == "@literal") { output = mod->add_literal(migraphx::from_value(node.at("literal"))); } else { auto op = make_op(name, fields); std::vector inputs; std::transform(node.at("inputs").begin(), node.at("inputs").end(), std::back_inserter(inputs), [&](const value& i) { auto i_name = i.to(); assert(contains(instructions, i_name)); return instructions.at(i_name); }); std::vector module_inputs; if(node.contains("module_inputs")) { std::transform(node.at("module_inputs").begin(), node.at("module_inputs").end(), std::back_inserter(module_inputs), [&](const value& i) { return map_mods.at(i.to()); }); for(auto& smod : module_inputs) { mod_from_val(smod, v, instructions, map_mods); } } if(name == "@return") { output = mod->add_return(inputs); } else if(module_inputs.empty()) { output = mod->add_instruction(op, inputs); } else { output = mod->add_instruction(op, inputs, module_inputs); } } output->set_normalized(normalized); instructions[node.at("output").to()] = output; } } void program::from_value(const value& v) { auto version = v.at("version").to(); if(version != program_file_version) { MIGRAPHX_THROW("Warning: Program version mismatch"); } this->impl->target_name = v.at("target").to(); if(not this->impl->target_name.empty()) { target t = make_target(this->impl->target_name); this->impl->ctx = t.get_context(); this->impl->ctx.from_value(v.at("context")); } auto module_vals = v.at("modules"); std::unordered_map map_mods; for(const auto& vv : module_vals) { const auto& name = vv.at("name").to(); if(name == "main") continue; impl->modules.push_back({name}); map_mods[name] = &impl->modules.back(); } std::unordered_map map_insts; auto* mm = get_main_module(); mod_from_val(mm, module_vals, map_insts, map_mods); this->finalize(); } double common_average(const std::vector& v) { std::size_t n = v.size() / 4; double total = std::accumulate(v.begin() + n, v.end() - n, 0.0); return total / std::distance(v.begin() + n, v.end() - n); } void program::perf_report(std::ostream& os, std::size_t n, parameter_map params) const { using milliseconds = std::chrono::duration; auto& ctx = this->impl->ctx; // Run once by itself eval(params); ctx.finish(); // Run and time entire program std::vector total_vec; total_vec.reserve(n); for(std::size_t i = 0; i < n; i++) { total_vec.push_back(time([&] { eval(params); ctx.finish(); })); } std::sort(total_vec.begin(), total_vec.end()); std::unordered_map> ins_vec; // Fill the map generic_eval(*this, ctx, params, [&](auto ins, auto) { ins_vec[ins].reserve(n); return argument{}; }); // Run and time each instruction for(std::size_t i = 0; i < n; i++) { generic_eval(*this, ctx, params, [&](auto ins, auto f) { argument result; ins_vec[ins].push_back(time([&] { result = f(); ctx.finish(); })); return result; }); } for(auto&& p : ins_vec) std::sort(p.second.begin(), p.second.end()); // Run and time implicit overhead std::vector overhead_vec; overhead_vec.reserve(n); for(std::size_t i = 0; i < n; i++) { overhead_vec.push_back(time([&] { dry_run(params); })); } double total_time = common_average(total_vec); double rate = 1000.0 / total_time; double overhead_time = common_average(overhead_vec); double overhead_percent = overhead_time * 100.0 / total_time; double total_instruction_time = 0.0; std::unordered_map op_times; for(auto&& p : ins_vec) { double avg = common_average(p.second); op_times[p.first->name()] += avg; total_instruction_time += avg; } double calculate_overhead_time = total_time - total_instruction_time; double calculate_overhead_percent = calculate_overhead_time * 100.0 / total_time; std::unordered_map names; this->print(names, [&](auto ins, auto ins_names) { instruction::print(std::cout, ins, ins_names); // skip return instruction if(ins->name() == "@return") return; double avg = common_average(ins_vec[ins]); double percent = std::ceil(100.0 * avg / total_instruction_time); os << ": " << avg << "ms, " << percent << "%"; os << std::endl; }); os << std::endl; os << "Summary:" << std::endl; std::vector> op_times_sorted; std::transform(op_times.begin(), op_times.end(), std::back_inserter(op_times_sorted), [](auto p) { return std::make_pair(p.second, p.first); }); std::sort(op_times_sorted.begin(), op_times_sorted.end(), std::greater<>{}); for(auto&& p : op_times_sorted) { auto&& name = p.second; double avg = p.first; double percent = std::ceil(100.0 * avg / total_instruction_time); os << name << ": " << avg << "ms, " << percent << "%" << std::endl; } os << std::endl; os << "Rate: " << rate << "/sec" << std::endl; os << "Total time: " << total_time << "ms" << std::endl; os << "Total instructions time: " << total_instruction_time << "ms" << std::endl; os << "Overhead time: " << overhead_time << "ms" << ", " << calculate_overhead_time << "ms" << std::endl; os << "Overhead: " << std::round(overhead_percent) << "%" << ", " << std::round(calculate_overhead_percent) << "%" << std::endl; } void program::debug_print() const { std::cout << *this << std::endl; } void program::debug_print(instruction_ref ins) const { std::unordered_map names; if(std::any_of(this->impl->modules.begin(), this->impl->modules.end(), [&](const auto& it) { return (it.end() == ins); })) { std::cout << "End instruction" << std::endl; return; } else if(std::none_of(this->impl->modules.begin(), this->impl->modules.end(), [&](const auto& it) { return it.has_instruction(ins); })) { std::cout << "Instruction not part of program" << std::endl; return; } std::stringstream ss; this->print(names, [&](auto x, auto ins_names) { if(x == ins) { instruction::print(std::cout, x, ins_names); std::cout << std::endl; } }); } void program::print( std::unordered_map& names, const std::function)>& print_func) const { for(const auto& mod : this->impl->modules) { std::cout << mod.name() << ":" << std::endl; mod.print(print_func, names); } } void program::print_graph(std::ostream& os, bool brief) const { const auto* mm = this->get_main_module(); mm->print_graph(os, brief); } void program::print_cpp(std::ostream& os) const { auto vec_modules = this->get_modules(); std::unordered_map names; for(auto& mod : vec_modules) { os << "module: \"" << mod->name() << "\"" << std::endl; names = mod->print_cpp(os, names); os << std::endl; } } void program::dry_run(std::unordered_map params) const { auto& ctx = this->impl->ctx; generic_eval(*this, ctx, std::move(params), [](auto&&...) { return argument{}; }); } void program::annotate(std::ostream& os, const std::function& a) const { for(auto& mod : this->impl->modules) { std::cout << mod.name() << ":" << std::endl; mod.annotate(os, a); } } const module* program::get_module(const std::string& name) const { auto it = std::find_if( impl->modules.begin(), impl->modules.end(), [&](auto& m) { return (m.name() == name); }); if(it == impl->modules.end()) { return nullptr; } return &(*it); } module* program::create_module(const std::string& name) { auto it = impl->modules.insert(impl->modules.end(), {name}); return &(*it); } module* program::get_module(const std::string& name) { auto it = std::find_if( impl->modules.begin(), impl->modules.end(), [&](auto& m) { return (m.name() == name); }); if(it == impl->modules.end()) { return nullptr; } return &(*it); } module* program::get_main_module() { return get_module("main"); } const module* program::get_main_module() const { return get_module("main"); } std::vector program::get_modules() const { const module* mm = get_main_module(); std::vector vec_modules; vec_modules.push_back(mm); auto sub_modules = mm->get_sub_modules(); vec_modules.insert(vec_modules.end(), sub_modules.begin(), sub_modules.end()); return vec_modules; } program& program::sort() { for(auto& mod : this->impl->modules) { mod.sort(); } return *this; } bool operator==(const program& x, const program& y) { return to_string(x) == to_string(y); } std::ostream& operator<<(std::ostream& os, const program& p) { auto vec_modules = p.get_modules(); std::unordered_map names; for(auto& mod : vec_modules) { os << "module: \"" << mod->name() << "\"" << std::endl; names = mod->print( [&](auto ins, auto ins_names) { instruction::print(os, ins, ins_names); os << std::endl; }, names); os << std::endl; } return os; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx