* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#include "codegen_tiling.h"
#include "codegen_tiling_data.h"
#include "common_utils.h"
namespace codegen {
using namespace ascgen_utils;
namespace {
void AppendTopnEntryInitialization(std::stringstream &ss) {
ss << " tiling_datas.clear();" << std::endl;
ss << " workspaces.clear();" << std::endl;
ss << " block_dims.clear();" << std::endl;
ss << " if (topn <= 0) {" << std::endl;
ss << " OP_LOGE(OP_NAME, \"GenerateTopnSolutions failed: invalid topn.\");" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
}
}
void TilingLib::GenReprScheduleGroupFields(std::stringstream &ss, const ascir::ScheduleGroup &sg,
const std::string &field_prefix, const std::string &emit_fn,
const std::string &indent, bool emit_first_arg) const {
std::unordered_set<std::string> seen_vars;
std::set<int64_t> q_ids;
std::set<int64_t> b_ids;
std::string first_arg = emit_first_arg ? ", first); first = false;" : ");";
for (size_t gi = 0; gi < sg.impl_graphs.size(); ++gi) {
const auto &graph = sg.impl_graphs[gi];
for (auto size : graph.GetAllSizeVar()) {
if (!size->expr.IsConstExpr()) {
std::string var_name = std::string(size->expr.Str().get());
if (seen_vars.find(var_name) == seen_vars.end()) {
ss << indent << emit_fn << "(\"" << var_name << "\", " << field_prefix << "get_" << var_name << "()"
<< first_arg << std::endl;
seen_vars.insert(var_name);
}
}
}
codegen::TilingData::GetTqueAndTbufId(graph, q_ids, b_ids);
codegen::TilingData::GetTmpBufName(graph, b_ids);
GenReprApiTilingFields(ss, graph, gi, field_prefix, emit_first_arg);
}
for (auto q_id : q_ids) {
if (q_id >= 0) {
ss << indent << emit_fn << "(\"q" << q_id << "_size\", " << field_prefix << "get_q" << q_id << "_size()"
<< first_arg << std::endl;
}
}
for (auto b_id : b_ids) {
if (b_id >= 0) {
ss << indent << emit_fn << "(\"b" << b_id << "_size\", " << field_prefix << "get_b" << b_id << "_size()"
<< first_arg << std::endl;
}
}
}
void TilingLib::GenReprApiTilingFields(std::stringstream &ss, const ascir::ImplGraph &graph, size_t tiling_case_id,
const std::string &field_prefix, bool top_level) const {
const std::string indent = top_level ? " " : " ";
const std::string first_flag = top_level ? "first" : "sub_first";
for (const auto &node : graph.GetAllNodes()) {
std::string device_type_name;
std::string api_field_name;
if (af::SUCCESS == GetApiTilingTypeName(node, device_type_name) &&
af::SUCCESS == GetApiTilingFieldName(node, api_field_name)) {
api_field_name = api_field_name + "_" + std::to_string(tiling_case_id);
ss << indent << "{" << std::endl;
ss << indent << " if (!" << first_flag << ") { repr << \",\"; }" << std::endl;
ss << indent << " repr << std::endl << \"" << indent << "." << api_field_name << " = {\";" << std::endl;
std::vector<std::string> api_fields;
codegen::TilingData::GetApiTilingDataName(node, api_fields);
bool api_first = true;
for (const auto &af : api_fields) {
ss << indent << " if (!" << (api_first ? "true" : "false") << ") { repr << \",\"; }" << std::endl;
ss << indent << " repr << std::endl << \"" << indent << " ." << af << " = \" << " << field_prefix
<< api_field_name << "." << af << ";" << std::endl;
api_first = false;
}
ss << indent << " repr << std::endl << \"" << indent << "}\";" << std::endl;
ss << indent << " " << first_flag << " = false;" << std::endl;
ss << indent << "}" << std::endl;
}
}
}
std::string TilingLib::GenGetTilingDataReprFuncForInductor(const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &tiling) const {
std::stringstream ss;
ss << "// GetTilingDataRepr returns a valid C++ designated initializer string for " << tiling << "." << std::endl;
ss << "#pragma GCC diagnostic push" << std::endl;
ss << "#pragma GCC diagnostic ignored \"-Wreturn-type-c-linkage\"" << std::endl;
ss << "extern \"C\" std::string GetTilingDataRepr(const " << tiling << " *tiling_data)" << std::endl;
ss << "{" << std::endl;
ss << " if (tiling_data == nullptr) {" << std::endl;
ss << " return std::string();" << std::endl;
ss << " }" << std::endl;
ss << " std::stringstream repr;" << std::endl;
ss << " repr << \"" << tiling << "{\" << std::endl;" << std::endl;
ss << " auto emit_field = [&](const char *name, const auto &val, bool first) {" << std::endl;
ss << " if (!first) { repr << \",\"; }" << std::endl;
ss << " repr << std::endl << \" .\" << name << \" = \" << val;" << std::endl;
ss << " };" << std::endl;
ss << " bool first = true;" << std::endl;
ss << " emit_field(\"block_dim\", tiling_data->get_block_dim(), first); first = false;" << std::endl;
ss << " emit_field(\"corenum\", tiling_data->get_corenum(), first); first = false;" << std::endl;
ss << " emit_field(\"ub_size\", tiling_data->get_ub_size(), first); first = false;" << std::endl;
ss << " emit_field(\"hbm_size\", tiling_data->get_hbm_size(), first); first = false;" << std::endl;
std::vector<ascir::TensorId> workspace_ids =
ascgen_utils::GetWorkspaceTensorIdListInOneScheduleResult(fused_schedule_result);
std::sort(workspace_ids.begin(), workspace_ids.end());
for (auto workspace_id : workspace_ids) {
ss << " emit_field(\"workspace" << workspace_id << "\", tiling_data->get_workspace" << workspace_id
<< "(), first); first = false;" << std::endl;
}
if (ascgen_utils::IsSingleGroup(fused_schedule_result)) {
GenReprSingleGroup(ss, fused_schedule_result);
} else {
GenReprMultiGroup(ss, fused_schedule_result);
}
ss << " repr << std::endl << \"}\";" << std::endl;
ss << " return repr.str();" << std::endl;
ss << "}" << std::endl;
ss << "#pragma GCC diagnostic pop" << std::endl;
return ss.str();
}
void TilingLib::GenReprSingleGroup(std::stringstream &ss,
const ascir::FusedScheduledResult &fused_schedule_result) const {
ss << " emit_field(\"tiling_key\", tiling_data->get_tiling_key(), first); first = false;" << std::endl;
auto &sg = fused_schedule_result.node_idx_to_scheduled_results[0][0].schedule_groups[0];
GenReprScheduleGroupFields(ss, sg, "tiling_data->", "emit_field", " ", true);
}
void TilingLib::GenReprMultiGroup(std::stringstream &ss,
const ascir::FusedScheduledResult &fused_schedule_result) const {
for (size_t i = 0; i < fused_schedule_result.node_idx_to_scheduled_results.size(); ++i) {
ss << " emit_field(\"graph" << i << "_tiling_key\", tiling_data->get_graph" << i << "_tiling_key(), first);"
<< " first = false;" << std::endl;
}
for (size_t i = 0; i < fused_schedule_result.node_idx_to_scheduled_results.size(); ++i) {
const auto &scheduled_results = fused_schedule_result.node_idx_to_scheduled_results[i];
for (size_t j = 0; j < scheduled_results.size(); ++j) {
const auto &schedule_groups = scheduled_results[j].schedule_groups;
for (size_t k = 0; k < schedule_groups.size(); ++k) {
std::string sub_name =
"graph" + std::to_string(i) + "_result" + std::to_string(j) + "_g" + std::to_string(k) + "_tiling_data";
ss << " {" << std::endl;
ss << " if (!first) { repr << \",\"; }" << std::endl;
ss << " repr << std::endl << \" ." << sub_name << " = {\";" << std::endl;
ss << " bool sub_first = true;" << std::endl;
ss << " auto emit_sub = [&](const char *name, const auto &val) {" << std::endl;
ss << " if (!sub_first) { repr << \",\"; }" << std::endl;
ss << " repr << std::endl << \" .\" << name << \" = \" << val;" << std::endl;
ss << " sub_first = false;" << std::endl;
ss << " };" << std::endl;
ss << " emit_sub(\"block_dim\", tiling_data->" << sub_name << ".get_block_dim());" << std::endl;
ss << " emit_sub(\"corenum\", tiling_data->" << sub_name << ".get_corenum());" << std::endl;
ss << " emit_sub(\"ub_size\", tiling_data->" << sub_name << ".get_ub_size());" << std::endl;
ss << " emit_sub(\"hbm_size\", tiling_data->" << sub_name << ".get_hbm_size());" << std::endl;
ss << " emit_sub(\"tiling_key\", tiling_data->" << sub_name << ".get_tiling_key());" << std::endl;
std::string field_prefix = "tiling_data->" + sub_name + ".";
GenReprScheduleGroupFields(ss, schedule_groups[k], field_prefix, "emit_sub", " ", false);
ss << " repr << std::endl << \" }\";" << std::endl;
ss << " first = false;" << std::endl;
ss << " }" << std::endl;
}
}
}
}
std::string TilingLib::GenUpdateCurPerfAndBlockByGroupHelper() const {
return ascgen_utils::GenUpdateCurPerfAndBlockByGroupHelper(false, true);
}
std::string TilingLib::GenEvaluateModeledPerfForInductor(
const std::string &tiling, const ::ascir::FusedScheduledResult &fused_schedule_result) const {
std::stringstream ss;
bool is_single_group = ascgen_utils::IsSingleGroup(fused_schedule_result);
ss << "static double EvaluateModeledPerf(const " << tiling << " &tiling_data) {" << std::endl;
if (is_single_group) {
ss << " " << tiling << " tmp = tiling_data;" << std::endl;
ss << " return optiling::GetPerf(tmp);" << std::endl;
} else {
GenMultiGroupPerfAggregation(ss, fused_schedule_result);
}
ss << "}" << std::endl;
return ss.str();
}
void TilingLib::GenMultiGroupPerfAggregation(std::stringstream &ss,
const ::ascir::FusedScheduledResult &fused_schedule_result) const {
const auto &node_results = fused_schedule_result.node_idx_to_scheduled_results;
ss << " double cur_perf = 0.0;" << std::endl;
ss << " double cur_tmp_perf = 0.0;" << std::endl;
ss << " uint32_t cur_block = 0;" << std::endl;
ss << " uint32_t limited_block = tiling_data.get_block_dim();" << std::endl;
bool first_result = true;
for (size_t asc_graph_id = 0; asc_graph_id < node_results.size(); ++asc_graph_id) {
const auto &scheduled_results = node_results[asc_graph_id];
for (size_t result_id = 0; result_id < scheduled_results.size(); ++result_id) {
if (first_result) {
ss << " if (tiling_data.get_graph" << asc_graph_id << "_tiling_key() == " << result_id << ") {" << std::endl;
first_result = false;
} else {
ss << " } else if (tiling_data.get_graph" << asc_graph_id << "_tiling_key() == " << result_id << ") {"
<< std::endl;
}
GenGroupPerfForScheduleResult(ss, asc_graph_id, result_id, scheduled_results[result_id]);
}
if (!scheduled_results.empty()) {
ss << " }" << std::endl;
}
}
ss << " return cur_perf;" << std::endl;
}
void TilingLib::GenGroupPerfForScheduleResult(std::stringstream &ss, size_t asc_graph_id, size_t result_id,
const ::ascir::ScheduledResult &sched_result) const {
const auto &schedule_groups = sched_result.schedule_groups;
bool enable_group_parallel = sched_result.enable_group_parallel && schedule_groups.size() > 1;
if (schedule_groups.size() == 1 || !enable_group_parallel) {
bool first_group = true;
for (size_t group_id = 0; group_id < schedule_groups.size(); ++group_id) {
std::string ns = "AscGraph" + std::to_string(asc_graph_id) + "ScheduleResult" + std::to_string(result_id) + "G" +
std::to_string(group_id);
std::string item = "graph" + std::to_string(asc_graph_id) + "_result" + std::to_string(result_id) + "_g" +
std::to_string(group_id) + "_tiling_data";
ss << " { auto _tmp = tiling_data." << item << "; ";
if (first_group) {
ss << "cur_perf = " << ns << "::GetPerf(_tmp); }" << std::endl;
first_group = false;
} else {
ss << "cur_perf += " << ns << "::GetPerf(_tmp); }" << std::endl;
}
}
} else {
bool first_group = true;
for (size_t group_id = 0; group_id < schedule_groups.size(); ++group_id) {
std::string ns = "AscGraph" + std::to_string(asc_graph_id) + "ScheduleResult" + std::to_string(result_id) + "G" +
std::to_string(group_id);
std::string item = "graph" + std::to_string(asc_graph_id) + "_result" + std::to_string(result_id) + "_g" +
std::to_string(group_id) + "_tiling_data";
if (first_group) {
ss << " { auto _tmp = tiling_data." << item << "; "
<< "cur_tmp_perf = " << ns << "::GetPerf(_tmp); }" << std::endl;
ss << " cur_block = tiling_data." << item << ".get_block_dim();" << std::endl;
first_group = false;
} else {
ss << " { auto _tmp = tiling_data." << item << "; "
<< "(void)UpdateCurPerfAndBlockByGroup({tiling_data." << item << ".get_block_dim(), " << ns
<< "::GetPerf(_tmp)}, limited_block, cur_block, cur_perf, "
<< "cur_tmp_perf); }" << std::endl;
}
}
ss << " cur_perf += cur_tmp_perf;" << std::endl;
}
}
std::string TilingLib::GenGetTopnSolutionsFuncForInductor(const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &tiling) const {
std::stringstream ss;
codegen::PgoShapeStringStream pgo_shape_dim;
int symbol_value_count = 0;
for (auto vars : fused_schedule_result.origin_vars) {
if (!(vars.IsConstExpr())) {
std::string var_define = std::string(vars.Str().get());
pgo_shape_dim.shape_dim_def << "int64_t " << var_define << ", ";
pgo_shape_dim.shape_dim_use << var_define << ", ";
TilingSetShapeDim(pgo_shape_dim.tiling_set_shape_dim, var_define, fused_schedule_result);
symbol_value_count++;
}
}
GenTopnGetTilingFunc(ss, fused_schedule_result, tiling, symbol_value_count);
GenGenerateTopnSolutionsEntry(ss, fused_schedule_result, tiling, pgo_shape_dim);
return ss.str();
}
void TilingLib::GenTopnInitSearchTiling(std::stringstream &ss, const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &tiling, int symbol_value_count) const {
ss << " const ResLimit *limit = (request.res_limit == nullptr || request.res_limit->aiv_num == 0) "
<< "? &g_no_limit_res : request.res_limit;" << std::endl;
ss << " if (request.symbol_values.size() != " << symbol_value_count << "ULL) {" << std::endl;
ss << " response.error_message = \"symbol_values size mismatch\";" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
ss << std::endl;
ss << " " << tiling << " search_tiling = {};" << std::endl;
ss << " search_tiling.set_block_dim(limit->aiv_num);" << std::endl;
ss << " search_tiling.set_ub_size(limit->ub_size - 256);" << std::endl;
{
int idx = 0;
for (auto vars : fused_schedule_result.origin_vars) {
if (!(vars.IsConstExpr())) {
std::string var_define = std::string(vars.Str().get());
ss << " const uint32_t " << var_define << " = static_cast<uint32_t>(request.symbol_values[" << idx << "]);"
<< std::endl;
TilingSetShapeDim(ss, var_define, fused_schedule_result, "search_tiling.");
idx++;
}
}
}
ss << std::endl;
}
void TilingLib::GenTopnGetTilingFunc(std::stringstream &ss, const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &tiling, int symbol_value_count) const {
ss << "static int64_t GetTopnCandidateSolutions(const GetTilingRequest &request, GetTilingResponse &response) {"
<< std::endl;
ss << " response.candidate_solutions.clear();" << std::endl;
ss << " response.error_message.clear();" << std::endl;
ss << " OP_LOGI(OP_NAME, \"GetTopnCandidateSolutions enter: topn=%ld, symbol_values.size=%zu, input_configs=%s\", "
<< "static_cast<long>(request.topn), request.symbol_values.size(), "
<< "request.input_configs == nullptr ? \"null\" : \"present\");" << std::endl;
ss << " if (request.topn <= 0) {" << std::endl;
GenTopnSetFailureMessage(ss, " ", "invalid topn");
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
GenTopnInitSearchTiling(ss, fused_schedule_result, tiling, symbol_value_count);
GenTopnDefaultTiling(ss, tiling);
ss << " const bool internal_no_config_path = (request.input_configs == nullptr);" << std::endl;
ss << " const bool explicit_no_config_path = request.input_configs != nullptr && request.input_configs->size() == 1 "
<< "&& request.input_configs->front().empty();" << std::endl;
ss << " const bool original_config_path = internal_no_config_path || explicit_no_config_path;" << std::endl;
ss << " std::vector<SearchConfig> configs;" << std::endl;
ss << " std::vector<const SearchConfig *> config_ptrs;" << std::endl;
ss << " if (original_config_path) {" << std::endl;
ss << " config_ptrs.push_back(nullptr);" << std::endl;
ss << " } else {" << std::endl;
ss << " configs = ParseSearchConfigs(*request.input_configs);" << std::endl;
ss << " if (configs.empty()) {" << std::endl;
GenTopnSetFailureMessage(ss, " ", "invalid input configs");
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
ss << " config_ptrs.reserve(configs.size());" << std::endl;
ss << " for (const auto &cfg : configs) { config_ptrs.push_back(&cfg); }" << std::endl;
ss << " }" << std::endl;
ss << std::endl;
GenTopnSearchAndFinalChecks(ss, tiling, fused_schedule_result);
ss << " return 0;" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
}
void TilingLib::GenTopnSearchTilingSetup(std::stringstream &ss, const std::string &tiling,
const ascir::FusedScheduledResult &fused_schedule_result) const {
ss << " for (const auto *cfg : config_ptrs) {" << std::endl;
ss << " if (cfg == nullptr) {" << std::endl;
ss << " OP_LOGI(OP_NAME, \"config: original tiling config\");" << std::endl;
ss << " } else {" << std::endl;
ss << " OP_LOGI(OP_NAME, \"config: ub_thresh=%.3f(enabled=%d), corenum_thresh=%.3f(enabled=%d), "
<< "multicore_ub_tradeoff=%d\", cfg->ub_threshold, cfg->ub_threshold_enabled, "
<< "cfg->corenum_threshold, cfg->corenum_threshold_enabled, cfg->enable_multicore_ub_tradeoff);" << std::endl;
ss << " }" << std::endl;
ss << " std::vector<AutofuseTilingDataPerf> raw_candidates;" << std::endl;
ss << " " << tiling << " cur_search_tiling = search_tiling;" << std::endl;
ss << " double best_perf = DBL_MAX;" << std::endl;
ss << " bool helper_ret = false;" << std::endl;
const bool is_single_group = ascgen_utils::IsSingleGroup(fused_schedule_result);
if (is_single_group) {
ss << " std::unordered_map<int64_t, uint64_t> workspace_map;" << std::endl;
}
GenTopnSearchTilingKeyCall(ss, fused_schedule_result, "cfg");
ss << " if (!helper_ret) {" << std::endl;
ss << " ++failed_config_count;" << std::endl;
ss << " response.error_message = \"PGOSearchTilingKey failed for topn config\";" << std::endl;
ss << " OP_LOGW(OP_NAME, \"PGOSearchTilingKey failed for topn config, failed=%zu/%zu.\", "
<< "failed_config_count, config_ptrs.size());" << std::endl;
ss << " continue;" << std::endl;
ss << " }" << std::endl;
ss << " OP_LOGI(OP_NAME, \"PGOSearchTilingKey returned %zu raw_candidates, best_perf=%.6f\", "
<< "raw_candidates.size(), best_perf);" << std::endl;
ss << " if (raw_candidates.empty()) {" << std::endl;
ss << " response.error_message = \"PGOSearchTilingKey returned no raw candidate\";" << std::endl;
ss << " OP_LOGW(OP_NAME, \"PGOSearchTilingKey returned no raw candidate for topn config.\");" << std::endl;
ss << " continue;" << std::endl;
ss << " }" << std::endl;
}
void TilingLib::GenTopnCollectCandidates(std::stringstream &ss, const std::string &tiling) const {
(void)tiling;
ss << " for (const auto &raw_candidate : raw_candidates) {" << std::endl;
ss << " CandidateSolution solution;" << std::endl;
ss << " solution.tiling_data = raw_candidate.tiling_data;" << std::endl;
ss << " solution.canonical_repr = GetTilingDataRepr(&raw_candidate.tiling_data);" << std::endl;
ss << " if (solution.canonical_repr.empty()) { continue; }" << std::endl;
ss << " double final_modeled_perf = EvaluateModeledPerf(raw_candidate.tiling_data);" << std::endl;
ss << " if (!std::isfinite(final_modeled_perf)) { final_modeled_perf = DBL_MAX; }" << std::endl;
ss << " solution.modeled_perf = final_modeled_perf;" << std::endl;
ss << " solution.is_default = !default_repr.empty() && (solution.canonical_repr == default_repr);" << std::endl;
ss << " if (solution.is_default) { found_default_candidate = true; }" << std::endl;
ss << " OP_LOGI(OP_NAME, \"candidate: repr=%s perf=%.6f is_default=%d\", "
<< "solution.canonical_repr.c_str(), solution.modeled_perf, solution.is_default);" << std::endl;
ss << " response.candidate_solutions.push_back(solution);" << std::endl;
ss << " }" << std::endl;
ss << " }" << std::endl;
ss << " if (!default_repr.empty() && !found_default_candidate) {" << std::endl;
ss << " CandidateSolution default_solution;" << std::endl;
ss << " default_solution.tiling_data = default_tiling;" << std::endl;
ss << " default_solution.canonical_repr = default_repr;" << std::endl;
ss << " default_solution.modeled_perf = DBL_MAX;" << std::endl;
ss << " default_solution.is_default = true;" << std::endl;
ss << " found_default_candidate = true;" << std::endl;
ss << " response.candidate_solutions.push_back(default_solution);" << std::endl;
ss << " }" << std::endl;
ss << std::endl;
}
void TilingLib::GenTopnSearchTilingKeyCall(std::stringstream &ss,
const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &search_cfg) const {
ss << " helper_ret = optiling::PGOSearchTilingKey(raw_candidates, cur_search_tiling, -1, &cur_search_tiling, ";
ss << "nullptr, ";
const bool is_single_group = ascgen_utils::IsSingleGroup(fused_schedule_result);
if (is_single_group) {
ss << "nullptr, 0, best_perf, workspace_map, {}, " << search_cfg << ");" << std::endl;
} else {
ss << "nullptr, 0, best_perf, " << search_cfg << ");" << std::endl;
}
}
void TilingLib::GenTopnSetFailureMessage(std::stringstream &ss, const std::string &indent,
const std::string &reason) const {
ss << indent << "response.error_message = \"" << reason << "\";" << std::endl;
}
void TilingLib::GenTopnDefaultTiling(std::stringstream &ss, const std::string &tiling) const {
ss << " std::string default_repr;" << std::endl;
ss << " bool found_default_candidate = false;" << std::endl;
ss << " " << tiling << " default_tiling = search_tiling;" << std::endl;
ss << " if (GetTiling(default_tiling, -1)) {" << std::endl;
ss << " default_repr = GetTilingDataRepr(&default_tiling);" << std::endl;
ss << " } else {" << std::endl;
ss << " OP_LOGW(OP_NAME, \"GetTiling failed for default topn config.\");" << std::endl;
ss << " response.error_message = \"GetTiling failed for default topn config\";" << std::endl;
ss << " }" << std::endl;
}
void TilingLib::GenTopnSearchAndFinalChecks(std::stringstream &ss, const std::string &tiling,
const ascir::FusedScheduledResult &fused_schedule_result) const {
ss << " PgoConfig::Instance().ResetRuntimeOverrides();" << std::endl;
ss << " size_t failed_config_count = 0U;" << std::endl;
GenTopnSearchTilingSetup(ss, tiling, fused_schedule_result);
GenTopnCollectCandidates(ss, tiling);
ss << " if (!found_default_candidate) {" << std::endl;
ss << " if (response.error_message.empty()) {" << std::endl;
GenTopnSetFailureMessage(ss, " ", "default topn candidate not found");
ss << " }" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
ss << " OP_LOGI(OP_NAME, \"GetTopnCandidateSolutions collected %zu candidates\", "
"response.candidate_solutions.size());"
<< std::endl;
ss << " if (response.candidate_solutions.empty()) {" << std::endl;
ss << " if (response.error_message.empty()) {" << std::endl;
GenTopnSetFailureMessage(ss, " ", "no topn candidate solution found");
ss << " }" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
}
void TilingLib::GenGenerateTopnSolutionsEntry(std::stringstream &ss,
const ascir::FusedScheduledResult &fused_schedule_result,
const std::string &tiling,
const codegen::PgoShapeStringStream &pgo_shape_dim) const {
ss << "extern \"C\" int64_t GenerateTopnSolutions(";
ss << pgo_shape_dim.shape_dim_def.str();
ss << "const std::vector<std::map<std::string, std::string>> &input_configs, int64_t topn, ";
ss << "std::vector<" << tiling << "> &tiling_datas, std::vector<int64_t> &workspaces, ";
ss << "std::vector<int64_t> &block_dims, ResLimit *res_limit = nullptr)" << std::endl;
ss << "{" << std::endl;
AppendTopnEntryInitialization(ss);
ss << " OP_LOGI(OP_NAME, \"GenerateTopnSolutions enter: topn=%ld, input_configs.size=%zu\", "
<< "static_cast<long>(topn), input_configs.size());" << std::endl;
ss << " GetTilingRequest request;" << std::endl;
(void)fused_schedule_result;
ss << " request.symbol_values = {" << pgo_shape_dim.shape_dim_use.str() << "};" << std::endl;
ss << " if (input_configs.empty()) {" << std::endl;
ss << " request.input_configs = nullptr;" << std::endl;
ss << " } else {" << std::endl;
ss << " request.input_configs = &input_configs;" << std::endl;
ss << " }" << std::endl;
ss << " request.res_limit = res_limit;" << std::endl;
ss << " request.topn = topn;" << std::endl;
ss << " GetTilingResponse response;" << std::endl;
ss << " if (GetTopnCandidateSolutions(request, response) != 0) {" << std::endl;
ss << " if (response.error_message.empty()) { response.error_message = \"unknown topn candidate generation "
"failure\"; }"
<< std::endl;
ss << " OP_LOGE(OP_NAME, \"GenerateTopnSolutions failed: %s\", response.error_message.c_str());" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
ss << " SelectTopnCandidateSolutions(response.candidate_solutions, topn);" << std::endl;
ss << " if (response.candidate_solutions.empty()) {" << std::endl;
ss << " OP_LOGE(OP_NAME, \"GenerateTopnSolutions failed: no candidate after topn selection.\");" << std::endl;
ss << " return -1;" << std::endl;
ss << " }" << std::endl;
ss << " OP_LOGI(OP_NAME, \"SelectTopn: %zu solutions after dedup+sort+truncate (topn=%ld)\", "
<< "response.candidate_solutions.size(), static_cast<long>(topn));" << std::endl;
ss << " for (const auto &sol : response.candidate_solutions) {" << std::endl;
ss << " tiling_datas.push_back(sol.tiling_data);" << std::endl;
ss << " workspaces.push_back(static_cast<int64_t>(GetWorkspaceSize(sol.tiling_data)));" << std::endl;
ss << " block_dims.push_back(static_cast<int64_t>(sol.tiling_data.get_block_dim()));" << std::endl;
ss << " OP_LOGI(OP_NAME, \"output[%zu]: perf=%.6f is_default=%d block_dim=%ld repr=%s\", "
<< "tiling_datas.size() - 1, sol.modeled_perf, sol.is_default, "
<< "static_cast<long>(sol.tiling_data.get_block_dim()), sol.canonical_repr.c_str());" << std::endl;
ss << " }" << std::endl;
ss << " return 0;" << std::endl;
ss << "}" << std::endl;
}
std::string TilingLib::GenCandidateSolutionProtocolForInductor(const std::string &tiling) const {
std::stringstream ss;
ss << "// Candidate solution protocol for Inductor topn selection." << std::endl;
ss << "struct CandidateSolution {" << std::endl;
ss << " " << tiling << " tiling_data;" << std::endl;
ss << " double modeled_perf = 0.0;" << std::endl;
ss << " bool is_default = false;" << std::endl;
ss << " std::string canonical_repr;" << std::endl;
ss << "};" << std::endl;
ss << std::endl;
ss << "struct GetTilingRequest {" << std::endl;
ss << " std::vector<int64_t> symbol_values;" << std::endl;
ss << " const std::vector<std::map<std::string, std::string>> *input_configs = nullptr;" << std::endl;
ss << " ResLimit *res_limit = nullptr;" << std::endl;
ss << " int64_t topn = 1;" << std::endl;
ss << "};" << std::endl;
ss << std::endl;
ss << "struct GetTilingResponse {" << std::endl;
ss << " std::vector<CandidateSolution> candidate_solutions;" << std::endl;
ss << " std::string error_message;" << std::endl;
ss << "};" << std::endl;
ss << std::endl;
return ss.str();
}
void TilingLib::GenDeduplicateCandidateSolutions(std::stringstream &ss) const {
ss << "inline void DeduplicateCandidateSolutions(std::vector<CandidateSolution> &solutions) {" << std::endl;
ss << " std::unordered_map<std::string, size_t> repr_to_index;" << std::endl;
ss << " std::vector<CandidateSolution> deduplicated;" << std::endl;
ss << " deduplicated.reserve(solutions.size());" << std::endl;
ss << " for (const auto &solution : solutions) {" << std::endl;
ss << " if (solution.canonical_repr.empty()) { continue; }" << std::endl;
ss << " const auto iter = repr_to_index.find(solution.canonical_repr);" << std::endl;
ss << " if (iter == repr_to_index.end()) {" << std::endl;
ss << " repr_to_index.emplace(solution.canonical_repr, deduplicated.size());" << std::endl;
ss << " deduplicated.push_back(solution);" << std::endl;
ss << " continue;" << std::endl;
ss << " }" << std::endl;
ss << " auto &kept = deduplicated[iter->second];" << std::endl;
ss << " if (!(std::fabs(kept.modeled_perf - solution.modeled_perf) < 1e-8)) {" << std::endl;
ss << " OP_LOGW(OP_NAME, \"same repr with different modeled_perf, keep first: kept=%.6f, current=%.6f, "
"repr=%s\", "
<< "kept.modeled_perf, solution.modeled_perf, solution.canonical_repr.c_str());" << std::endl;
ss << " continue;" << std::endl;
ss << " }" << std::endl;
ss << " if (!kept.is_default && solution.is_default) {" << std::endl;
ss << " kept = solution;" << std::endl;
ss << " }" << std::endl;
ss << " }" << std::endl;
ss << " solutions.swap(deduplicated);" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
}
std::string TilingLib::GenTopnSelectorHelpersForInductor() const {
std::stringstream ss;
ss << "// Topn selector helpers: default-first, modeled_perf ascending, canonical_repr tiebreak." << std::endl;
ss << "inline bool CompareCandidateSolution(const CandidateSolution &lhs, const CandidateSolution &rhs) {"
<< std::endl;
ss << " if (lhs.is_default != rhs.is_default) { return lhs.is_default; }" << std::endl;
ss << " if (lhs.modeled_perf < rhs.modeled_perf || rhs.modeled_perf < lhs.modeled_perf) { return lhs.modeled_perf < "
"rhs.modeled_perf; }"
<< std::endl;
ss << " return lhs.canonical_repr < rhs.canonical_repr;" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
GenDeduplicateCandidateSolutions(ss);
ss << "inline void SelectTopnCandidateSolutions(std::vector<CandidateSolution> &solutions, int64_t topn) {"
<< std::endl;
ss << " const size_t before_dedup = solutions.size();" << std::endl;
ss << " DeduplicateCandidateSolutions(solutions);" << std::endl;
ss << " OP_LOGI(OP_NAME, \"DeduplicateCandidateSolutions: %zu -> %zu\", before_dedup, solutions.size());"
<< std::endl;
ss << " std::sort(solutions.begin(), solutions.end(), CompareCandidateSolution);" << std::endl;
ss << " for (size_t i = 0; i < solutions.size(); ++i) {" << std::endl;
ss << " OP_LOGI(OP_NAME, \"sorted[%zu]: perf=%.6f is_default=%d repr_len=%zu\", "
<< "i, solutions[i].modeled_perf, solutions[i].is_default, solutions[i].canonical_repr.size());" << std::endl;
ss << " const std::string &repr = solutions[i].canonical_repr;" << std::endl;
ss << " const size_t chunk = 800;" << std::endl;
ss << " for (size_t off = 0; off < repr.size(); off += chunk) {" << std::endl;
ss << " OP_LOGI(OP_NAME, \" repr[%zu..%zu]: %.*s\", off, std::min(off + chunk, repr.size()), "
<< "static_cast<int>(std::min(chunk, repr.size() - off)), repr.c_str() + off);" << std::endl;
ss << " }" << std::endl;
ss << " }" << std::endl;
ss << " if (topn > 0 && static_cast<int64_t>(solutions.size()) > topn) {" << std::endl;
ss << " OP_LOGI(OP_NAME, \"truncate %zu -> %ld\", solutions.size(), static_cast<long>(topn));" << std::endl;
ss << " solutions.resize(static_cast<size_t>(topn));" << std::endl;
ss << " }" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
return ss.str();
}
std::string TilingLib::GenSearchConfigProtocolForInductor() const {
std::stringstream ss;
ss << "// SearchConfig for dual-path PGO: TF builtin and Inductor request configs." << std::endl;
ss << "struct SearchConfig {" << std::endl;
ss << " bool ub_threshold_enabled = false;" << std::endl;
ss << " double ub_threshold = 0.0;" << std::endl;
ss << " bool corenum_threshold_enabled = false;" << std::endl;
ss << " double corenum_threshold = 1.0;" << std::endl;
ss << " bool enable_multicore_ub_tradeoff = false;" << std::endl;
ss << "};" << std::endl;
ss << std::endl;
return ss.str();
}
std::string TilingLib::GenBuiltinTfPgoConfigsForInductor() const {
std::stringstream ss;
ss << "// Builtin TF PGO search configs: 5 fixed threshold configurations." << std::endl;
ss << "inline std::vector<SearchConfig> GetBuiltinTfPgoConfigs() {" << std::endl;
ss << " return {" << std::endl;
ss << " {true, 0.2, true, 0.4, false}," << std::endl;
ss << " {true, 0.1, true, 0.4, false}," << std::endl;
ss << " {true, 0.0, true, 1.0, false}," << std::endl;
ss << " {true, 0.05, true, 1.0, false}," << std::endl;
ss << " {true, 0.1, true, 0.8, false}," << std::endl;
ss << " };" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
return ss.str();
}
std::string TilingLib::GenInductorConfigParserForInductor() const {
std::stringstream ss;
ss << "// Parse Inductor request configs from interface input." << std::endl;
ss << "constexpr double kMinUbThreshold = 0.001;" << std::endl;
ss << "inline bool ParseSearchConfig(const std::map<std::string, std::string> &raw, SearchConfig &out) {"
<< std::endl;
ss << " out = SearchConfig();" << std::endl;
ss << " auto ub_it = raw.find(\"ub_threshold\");" << std::endl;
ss << " if (ub_it != raw.end()) {" << std::endl;
ss << " out.ub_threshold_enabled = true;" << std::endl;
ss << " try { out.ub_threshold = std::stod(ub_it->second); } catch (...) { return false; }" << std::endl;
ss << " if (std::fabs(out.ub_threshold) < 1e-8) { out.ub_threshold = kMinUbThreshold; }" << std::endl;
ss << " }" << std::endl;
ss << " auto cn_it = raw.find(\"corenum_threshold\");" << std::endl;
ss << " if (cn_it != raw.end()) {" << std::endl;
ss << " out.corenum_threshold_enabled = true;" << std::endl;
ss << " try { out.corenum_threshold = std::stod(cn_it->second); } catch (...) { return false; }" << std::endl;
ss << " }" << std::endl;
ss << " auto mc_it = raw.find(\"enable_multicore_ub_tradeoff\");" << std::endl;
ss << " if (mc_it != raw.end()) {" << std::endl;
ss << " if (mc_it->second == \"true\") { out.enable_multicore_ub_tradeoff = true; }" << std::endl;
ss << " else if (mc_it->second == \"false\") { out.enable_multicore_ub_tradeoff = false; }" << std::endl;
ss << " else { return false; }" << std::endl;
ss << " }" << std::endl;
ss << " for (const auto &kv : raw) {" << std::endl;
ss << " if (kv.first != \"ub_threshold\" && kv.first != \"corenum_threshold\"" << std::endl;
ss << " && kv.first != \"enable_multicore_ub_tradeoff\") { return false; }" << std::endl;
ss << " }" << std::endl;
ss << " return true;" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
ss << "inline std::vector<SearchConfig> ParseSearchConfigs(" << std::endl;
ss << " const std::vector<std::map<std::string, std::string>> &raws) {" << std::endl;
ss << " std::vector<SearchConfig> result;" << std::endl;
ss << " for (const auto &raw : raws) {" << std::endl;
ss << " SearchConfig cfg;" << std::endl;
ss << " if (!ParseSearchConfig(raw, cfg)) { return {};" << std::endl;
ss << " }" << std::endl;
ss << " result.push_back(cfg);" << std::endl;
ss << " }" << std::endl;
ss << " return result;" << std::endl;
ss << "}" << std::endl;
ss << std::endl;
return ss.str();
}
}