| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include <tvm/ffi/cast.h> |
| #include <tvm/ffi/reflection/registry.h> |
| #include <tvm/relax/expr.h> |
| #include <tvm/relax/expr_functor.h> |
| #include <tvm/s_tir/meta_schedule/extracted_task.h> |
| #include <tvm/target/target.h> |
| #include <tvm/tirx/function.h> |
| #include <tvm/tirx/stmt_functor.h> |
| |
| #include "../../s_tir/meta_schedule/module_equality.h" |
| |
| namespace tvm { |
| namespace relax { |
| namespace backend { |
| |
| using s_tir::meta_schedule::ExtractedTask; |
| using s_tir::meta_schedule::ModuleEqual; |
| using s_tir::meta_schedule::ModuleEquality; |
| using s_tir::meta_schedule::ModuleHash; |
| |
| /*! |
| * \brief Extract the Meta-Schedule tuning task from a given IRModule. |
| * \note |
| * 1. The task extractor is responsible for task deduplication. The |
| * deduplication is achieved by comparing structural hashes of PrimFuncs. |
| * 2. For a PrimFunc, the weight of its corresponding task is the number |
| * of times it called by op Call-TIR. Say in an IRModule there are three |
| * PrimFuncs `fn1`, `fn2` and `fn3` sharing the same structural hash. |
| * Suppose `fn1` is called by 5 Call-TIR ops among all Relax function, |
| * `fn2` is called by 3 Call-TIR and `fn3` is called by 5 Call-TIR. |
| * Then we will have a ExtractedTask for all three functions, whose weight |
| * is 5 + 3 + 2 = 10. |
| */ |
| class BlockCounter : public tirx::StmtVisitor { |
| public: |
| static size_t GetSBlockCount(const tirx::PrimFunc& func) { |
| BlockCounter counter; |
| counter(func->body); |
| return counter.count; |
| } |
| |
| private: |
| void VisitStmt_(const tirx::SBlockNode* op) final { |
| ++count; |
| StmtVisitor::VisitStmt_(op); |
| } |
| size_t count{0}; |
| }; |
| |
| class TaskExtractor : public ExprVisitor { |
| public: |
| static ffi::Array<ExtractedTask> ExtractTask(IRModule mod, Target target, |
| ffi::String mod_eq_name) { |
| TaskExtractor extractor(mod, target, mod_eq_name); |
| // We go through each Relax function in the module. |
| for (const auto& kv : mod->functions) { |
| if (const auto* func = kv.second.as<FunctionNode>()) { |
| extractor(ffi::GetRef<Function>(func)); |
| } |
| } |
| ffi::Array<ExtractedTask> tasks; |
| for (const auto& it : extractor.func2task_) { |
| tasks.push_back(it.second); |
| } |
| return tasks; |
| } |
| |
| private: |
| explicit TaskExtractor(IRModule mod, Target target, ffi::String mod_eq_name) |
| : mod_(std::move(mod)), |
| target_(std::move(target)), |
| mod_eq_(ModuleEquality::Create(mod_eq_name)), |
| func2task_(/*bucket_count*/ 0, ModuleHash(*mod_eq_), ModuleEqual(*mod_eq_)) { |
| normalize_mod_func_ = tvm::ffi::Function::GetGlobal("tvm.s_tir.meta_schedule.normalize_mod"); |
| TVM_FFI_ICHECK(normalize_mod_func_.has_value()) << "Normalization function is not found."; |
| } |
| |
| void VisitExpr_(const CallNode* call) final { |
| static const Op& call_tir_op = Op::Get("relax.call_tir"); |
| |
| // TODO(@tvm-team): When we differentiate the call for tirx function and packed function, |
| // this logic should be changed accordingly. |
| if (!call->op.same_as(call_tir_op)) { |
| // Since the Relax function is of A-normal form, the arguments of this call cannot be another |
| // Calls. And hence we do not need to recurse into this Call. |
| return; |
| } |
| |
| const GlobalVar& global_var = call->args[0].as_or_throw<GlobalVar>(); |
| const tirx::PrimFunc& func = mod_->Lookup(global_var).as_or_throw<tirx::PrimFunc>(); |
| IRModule mod = (*normalize_mod_func_)(func).cast<IRModule>(); |
| size_t weight = 1; |
| auto it = func2task_.find(mod); |
| if (it != func2task_.end()) { |
| it->second->weight += 1; |
| const tirx::PrimFunc& alt_func = it->first->Lookup("main").as_or_throw<tirx::PrimFunc>(); |
| // When anchor-block based equality is used, tuning tasks "nn_conv2d_add_nn_relu" and |
| // "nn_conv2d_add_add_nn_relu", for example, can be identified as equal. Thus, one of them |
| // will be selected to tune by the code below. |
| // |
| // To make sure that we tune "nn_conv2d_add_nn_relu" and not "nn_conv2d_add_add_nn_relu", we |
| // count the PrinFunc number of blocks and leave only the function with the smallest number of |
| // blocks. This way, "nn_conv2d_add_nn_relu" will have a smaller number of blocks than |
| // "nn_conv2d_add_add_nn_relu" and will be selected to tune. |
| if (BlockCounter::GetSBlockCount(func) < BlockCounter::GetSBlockCount(alt_func)) { |
| weight += it->second->weight; |
| func2task_.erase(it->first); |
| } |
| } |
| |
| ExtractedTask task(/*task_name=*/global_var->name_hint, // |
| /*mod=*/mod, // |
| /*target=*/target_, // |
| /*dispatched=*/{mod}, // |
| /*weight=*/weight); |
| func2task_.emplace(mod, task); |
| } |
| |
| IRModule mod_; |
| Target target_; |
| std::unique_ptr<ModuleEquality> mod_eq_; |
| std::unordered_map<IRModule, ExtractedTask, ModuleHash, ModuleEqual> func2task_; |
| std::optional<tvm::ffi::Function> normalize_mod_func_; |
| }; |
| |
| TVM_FFI_STATIC_INIT_BLOCK() { |
| namespace refl = tvm::ffi::reflection; |
| refl::GlobalDef().def("relax.backend.MetaScheduleExtractTask", [](IRModule mod, Target target, |
| ffi::String mod_eq_name) { |
| return TaskExtractor::ExtractTask(std::move(mod), std::move(target), std::move(mod_eq_name)); |
| }); |
| } |
| |
| } // namespace backend |
| } // namespace relax |
| } // namespace tvm |