Files
apache--tvm/include/tvm/ir/transform.h
T
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

585 lines
21 KiB
C++

/*
* 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.
*/
/*!
* \file tvm/ir/transform.h
*
* This file implements a pass manager. The pass manager manages a sequence
* of IRModule -> IRModule transformation passes over a particlar unit of AST. The
* design is largely inspired from LLVM's pass manager and modern deep learning
* frameworks that perform tensor->tensor transformations.
*
* The responsibilities of a traditional compiler pass manager usually involves:
* - Organizing the execution order of optimization passes though not
* necessarily in the optimal sequence.
* - Collecting required analysis information and keep them up-to-date.
* - Reducing the effort required to implement new passes for compiler
* developers, etc.
*
* Similar to LLVM's pass manager, we designed the Relax pass manager to work
* different granularity, i.e. module level, function level, and even sequential
* passe that contains a host of passes.
*
* However, we also extend the functionality of the traditional pass manager
* with the consideration of requirements/convention from deep learning
* frameworks, such as Pytorch and Gluon, etc. Each pass in the Relax pass
* manager performs the IRModule -> IRModule transformation. All
* different types of passes, including the sequential-level pass object, are
* essentially pass objects. This design, therefore, effectively provides users
* a consistent and convenient interface, i.e. Pass, to play with. It offers a
* means to ease the development and testing of Relax passes. For example, with
* the pass manager, external users will be able to have custom passes correctly
* scheduled without having to modify a single handcrafted pass order.
*
* In the future we need to describe constraints between passes. For example,
* we may want to preserve dependencies between different passes and validate
* them on the completion of a certain pass.
*
* We also need to store side information and import the error reporting system.
*/
#ifndef TVM_IR_TRANSFORM_H_
#define TVM_IR_TRANSFORM_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/creator.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/instrument.h>
#include <tvm/ir/module.h>
#include <tvm/ir/with_context.h>
#include <string>
#include <type_traits>
#include <utility>
namespace tvm {
namespace transform {
/*!
* \brief PassContextNode contains the information that a pass can rely on,
* such as analysis results.
* \sa PassContext
*/
class PassContextNode : public ffi::Object {
public:
/*! \brief The default optimization level. */
int opt_level{2};
/*! \brief The list of required passes. */
ffi::Array<ffi::String> required_pass;
/*! \brief The list of disabled passes. */
ffi::Array<ffi::String> disabled_pass;
/*! \brief Pass specific configurations. */
ffi::Map<ffi::String, Any> config;
/*! \brief A list of pass instrument implementations. */
ffi::Array<instrument::PassInstrument> instruments;
PassContextNode() = default;
/*!
* \brief Get a config value from the pass context.
*
* \param key The config key.
* \param default_value The default value if the key does not exist, defaults to nullptr.
*
* \return The result
*
* \tparam TOBjectRef the expected object type.
* \throw Error if the key exists but the value does not match TObjectRef.
*/
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetConfig(
const std::string& key,
ffi::Optional<TObjectRef> default_value = ffi::Optional<TObjectRef>(std::nullopt)) const {
if (!config.defined()) return default_value;
auto it = config.find(key);
if (it != config.end()) {
return (*it).second.as_or_throw<ffi::Optional<TObjectRef>>();
} else {
return default_value;
}
}
// variant that uses TObjectRef to enable implicit conversion to default value.
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetConfig(const std::string& key, TObjectRef default_value) const {
return GetConfig<TObjectRef>(key, ffi::Optional<TObjectRef>(default_value));
}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PassContextNode>()
.def_ro("opt_level", &PassContextNode::opt_level)
.def_ro("required_pass", &PassContextNode::required_pass)
.def_ro("disabled_pass", &PassContextNode::disabled_pass)
.def_ro("instruments", &PassContextNode::instruments)
.def_ro("config", &PassContextNode::config);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.PassContext", PassContextNode, ffi::Object);
};
/*!
* \brief PassContext that is used to configure the pass behavior.
*
* \code
*
* auto new_ctx = PassContext::Create();
* ctx->opt_level = 2;
* With<PassContext> scope(ctx);
* // pass context in effect.
*
* \endcode
* \sa PassContextNode
*/
class PassContext : public ffi::ObjectRef {
public:
PassContext() {}
/*!
* \brief constructor with UnsafeInit
*/
explicit PassContext(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
/*!
* \brief constructor with ffi::ObjectPtr
*/
explicit PassContext(ffi::ObjectPtr<PassContextNode> n) : ffi::ObjectRef(n) {}
/*!
* \brief const accessor.
* \return const access pointer.
*/
const PassContextNode* operator->() const {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<const PassContextNode*>(get());
}
/*!
* \brief mutable accessor.
* \return mutable access pointer.
*/
PassContextNode* operator->() {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<PassContextNode*>(get_mutable());
}
/*!
* \brief Construct a PassContext containing the default configurations.
* \return The new PassContext.
*/
TVM_DLL static PassContext Create();
/*!
* \brief Get the default pass context in the current scope.
* \return The pass context.
*/
TVM_DLL static PassContext Current();
/*!
* \brief Get all supported configuration names and metadata, registered within the PassContext.
* \return Map indexed by the config name, pointing to the metadata map as key-value
*/
TVM_DLL static ffi::Map<ffi::String, ffi::Map<ffi::String, ffi::String>> ListConfigs();
/*!
* \brief Call instrument implementations' callbacks when entering PassContext.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*/
TVM_DLL void InstrumentEnterPassContext();
/*!
* \brief Call instrument implementations' callbacks when exiting PassContext.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*/
TVM_DLL void InstrumentExitPassContext();
/*!
* \brief Call instrument implementations' callbacks before a pass run.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*
* \return false: the pass is skipped; true: the pass runs.
*/
TVM_DLL bool InstrumentBeforePass(const IRModule& mod, const PassInfo& info) const;
/*!
* \brief Call instrument implementations callbacks after a pass run.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*/
TVM_DLL void InstrumentAfterPass(const IRModule& mod, const PassInfo& info) const;
/*!
* \brief Check whether a pass is enabled.
* \param info The pass information.
* \return true if the pass is enabled. Otherwise, false.
*/
TVM_DLL bool PassEnabled(const PassInfo& info) const;
/*!
* \brief Register a valid configuration option and its ValueType for validation.
*
* \param key The configuration key.
* \tparam ValueType The value type to be registered
*/
template <typename ValueType>
static int32_t RegisterConfigOption(const char* key) {
// NOTE: we could further update the function later.
if constexpr (std::is_base_of_v<ffi::ObjectRef, ValueType>) {
int32_t tindex = ffi::TypeToRuntimeTypeIndex<ValueType>::v();
auto type_key = ffi::TypeIndexToTypeKey(tindex);
auto legalization = [=](ffi::Any value) -> ffi::Any {
if (auto opt_map = value.try_cast<ffi::Map<ffi::String, ffi::Any>>()) {
return ffi::reflection::ObjectCreator(type_key)(opt_map.value());
} else {
auto opt_val = value.try_cast<ValueType>();
if (!opt_val.has_value()) {
TVM_FFI_THROW(AttributeError)
<< "Expect config " << key << " to have type " << type_key << ", but instead get "
<< ffi::details::AnyUnsafe::GetMismatchTypeInfo<ValueType>(value);
}
return *opt_val;
}
};
RegisterConfigOption(key, type_key, legalization);
} else {
// non-object type, do not support implicit conversion from map
std::string type_str = ffi::TypeTraits<ValueType>::TypeStr();
auto legalization = [=](ffi::Any value) -> ffi::Any {
auto opt_val = value.try_cast<ValueType>();
if (!opt_val.has_value()) {
TVM_FFI_THROW(AttributeError)
<< "Expect config " << key << " to have type " << type_str << ", but instead get "
<< ffi::details::AnyUnsafe::GetMismatchTypeInfo<ValueType>(value);
} else {
return *opt_val;
}
};
RegisterConfigOption(key, type_str, legalization);
}
return 0;
}
// accessor.
using ContainerType = PassContextNode;
class Internal;
private:
// The entry of a pass context scope.
TVM_DLL void EnterWithScope();
// The exit of a pass context scope.
TVM_DLL void ExitWithScope();
// Register configuration key value type.
TVM_DLL static void RegisterConfigOption(const char* key, ffi::String value_type_str,
std::function<ffi::Any(ffi::Any)> legalization);
// Classes to get the Python `with` like syntax.
friend class Internal;
friend class With<PassContext>;
};
/*!
* \brief Create a pass-config object with all default values, using the
* reflection defaults.
* \tparam TConfig the ObjectRef type to be created.
* \return An instance with all reflection-defined default values applied.
*/
template <typename TConfig>
inline TConfig PassConfigWithDefaults() {
static_assert(std::is_base_of_v<ffi::ObjectRef, TConfig>,
"Can only create ObjectRef-derived types");
using ContainerType = typename TConfig::ContainerType;
static auto finit_object = ffi::Function::GetGlobalRequired("ffi.MakeObjectFromPackedArgs");
ffi::AnyView packed_args[1];
packed_args[0] = ContainerType::RuntimeTypeIndex();
ffi::Any rv;
finit_object.CallPacked(ffi::PackedArgs(packed_args, 1), &rv);
return rv.cast<TConfig>();
}
#define TVM_PASS_CTX_CONFIG_VAR_DEF [[maybe_unused]] static uint32_t __make_PassContext_tid
/*!
* \brief Helper macro to register the object type to runtime.
* Makes sure that the runtime type table is correctly populated.
*
* Use this macro in the cc file for each terminal class.
*/
#define TVM_REGISTER_PASS_CONFIG_OPTION(Key, ValueType) \
TVM_FFI_STR_CONCAT(TVM_PASS_CTX_CONFIG_VAR_DEF, __COUNTER__) = \
::tvm::transform::PassContext::RegisterConfigOption<ValueType>(Key)
/*!
* \brief Meta data that will be used to help optimization and analysis.
* \sa PassInfo
*/
class PassInfoNode : public ffi::Object {
public:
/*! \brief The minimal optimization level that this pass will be enabled. */
int opt_level;
/*! \brief The name of an optimization/analysis pass. */
ffi::String name;
/*! \brief Boolean that tells whether this pass will be traced or not. */
bool traceable;
/*! \brief The passes that are required to perform the current pass. */
ffi::Array<ffi::String> required;
PassInfoNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PassInfoNode>()
.def_ro("opt_level", &PassInfoNode::opt_level)
.def_ro("name", &PassInfoNode::name)
.def_ro("required", &PassInfoNode::required)
.def_ro("traceable", &PassInfoNode::traceable);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.PassInfo", PassInfoNode, ffi::Object);
};
/*!
* \brief Managed reference class for PassInfoNode
* \sa PassInfoNode
*/
class PassInfo : public ffi::ObjectRef {
public:
/*!
* \brief Constructor
* \param opt_level The optimization level
* \param name Name of the pass.
* \param required The passes that are required to perform the current pass.
* \param traceable Boolean that tells whether the pass is traceable.
*/
TVM_DLL PassInfo(int opt_level, ffi::String name, ffi::Array<ffi::String> required,
bool traceable);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PassInfo, ffi::ObjectRef, PassInfoNode);
};
/*!
* \brief PassNode is the base type of differnt types of optimization passes.
* It is designed as a pure class and implemented by different pass subclasses
* at different granularity of Relax nodes.
*/
class PassNode : public ffi::Object {
public:
virtual ~PassNode() {}
/*!
* \brief Get the pass information/meta data. */
virtual PassInfo Info() const = 0;
/*!
* \brief Transform mod using the default PassContext in the current scope.
*
* \param mod The module that an optimization pass runs on.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod) const {
return this->operator()(std::move(mod), PassContext::Current());
}
/*!
* \brief Transform mod using a functor under a given pass context.
*
* \param mod The module that an optimization pass runs on.
* \param pass_ctx The pass context that can provide information for the optimization.
*
* \return The transformed module.
*/
virtual IRModule operator()(IRModule mod, const PassContext& pass_ctx) const = 0;
TVM_FFI_DECLARE_OBJECT_INFO("transform.Pass", PassNode, ffi::Object);
};
class Pass : public ffi::ObjectRef {
public:
/*!
* \brief Transform mod using the default PassContext in the current scope.
*
* \code
*
* // If you do no longer need the input module
* // it is recommended to use std::move to move your input module.
* mod = pass(std::move(mod));
*
* \endcode
*
* \param mod The module that an optimization pass runs on.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod) const;
/*!
* \brief Transform mod using a functor under a given pass context.
*
* \param mod The module that an optimization pass runs on.
* \param pass_ctx The pass context that can provide information for the optimization.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod, const PassContext& pass_ctx) const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Pass, ffi::ObjectRef, PassNode);
private:
IRModule static AssertImmutableModule(const IRModule& mod, const PassNode* node,
const PassContext& pass_ctx);
};
/*!
* \brief The SequentialNode contains a set of passes that transform Relax
* programs from one AST to another semantically equivalent one.
*
* One example of this level of pass is that the pass manager needs to correctly
* perform a host of optimizations with a given optimization level and disabled
* passes.
*/
class SequentialNode : public PassNode {
public:
/* \brief The pass meta data.*/
PassInfo pass_info;
/*! \brief A list of passes that used to compose a sequential pass. */
tvm::ffi::Array<Pass> passes;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SequentialNode>()
.def_ro("pass_info", &SequentialNode::pass_info)
.def_ro("passes", &SequentialNode::passes);
}
/*!
* \brief Get the pass information/meta data.
*/
PassInfo Info() const override { return pass_info; }
/*!
* \brief Resolve the pass dependency. It globs all required passes by
* a given pass and executes them.
*
* \param mod The module that an optimization pass runs on.
*
* TODO(zhiics) Build a dependency graph among the passes using provided
* metadata, i.e. required_passes. Likely, we can have a data structure, i.e.
* PassInfo, to store the relevant information including the parent passes.
*/
void ResolveDependency(const IRModule& mod);
/*!
* \brief Perform optimizations on a series of passes. The aforementioned
* typical pass manager jobs could be done by it. This function could
* be overloaded to focus on different metrics, i.e. performance,
* memory footprint, etc.
*
* \param mod The module that these passes are applied on.
* \param pass_ctx The context that these passes execute on.
*
* \return Return the updated module.
*/
IRModule operator()(IRModule mod, const PassContext& pass_ctx) const final;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.Sequential", SequentialNode, PassNode);
};
class Sequential : public Pass {
public:
/*!
* \brief The constructor of `Sequential`.
*
* \param passes The passes to apply.
* \param pass_info The pass metadata.
*/
TVM_DLL Sequential(ffi::Array<Pass> passes, PassInfo pass_info);
/*!
* \brief The constructor of `Sequential`.
*
* \param passes The passes to apply.
* \param name The name of a sequential pass. It's defaulted to "sequential".
* This allows users to only provide a list of passes and execute them
* under a given context.
*/
TVM_DLL Sequential(ffi::Array<Pass> passes, ffi::String name = "sequential");
Sequential() = default;
explicit Sequential(ffi::ObjectPtr<SequentialNode> n) : Pass(n) {}
const SequentialNode* operator->() const;
using ContainerType = SequentialNode;
};
/*
* \brief Create a module pass.
*
* \param pass_func The packed function that contains the optimization.
* \param opt_level The optimization level of the module pass.
* \param name The name of the module pass.
* \param required The list of the passes that the module pass is dependent on.
*
* \return The created module pass.
*/
TVM_DLL Pass CreateModulePass(std::function<IRModule(IRModule, PassContext)> pass_func,
int opt_level, ffi::String name, ffi::Array<ffi::String> required,
bool traceable = false);
/*!
* \brief A special trace pass that prints the header and IR to LOG(INFO).
* \param header The header to be attached to the output.
* \return The pass.
*/
TVM_DLL Pass PrintIR(ffi::String header = "");
/*!
* \brief Enrich a pass-time error with a TVMScript-rendered, underlined source
* location derived from the error's embedded VisitErrorContext.
*
* Returns an ffi::Error that preserves err's kind, original message, and
* backtrace, and appends the failing pass name plus the offending location
* rendered as TVMScript (the whole \p mod, or local to \p func when provided).
* The returned error drops the VisitErrorContext payload, so an outer catch
* that re-enriches finds no context and returns the error unchanged.
*
* Pure and total: never throws; returns \p err unchanged when there is no
* context, the path is unresolvable, or rendering fails.
*
* \param err The error thrown by the pass body.
* \param mod The IRModule the pass ran on (the access-path root, or the
* container of \p func when \p func is provided).
* \param pass_name The name of the failing pass, shown in the message.
* \param func When set, resolve and render the location local to
* \p mod->functions[func]; otherwise use the whole module.
* \return The enriched (or, on any fallback, the original) error.
*/
TVM_DLL ffi::Error EnrichPassErrorWithContext(
const ffi::Error& err, const IRModule& mod, ffi::String pass_name,
ffi::Optional<GlobalVar> func = ffi::Optional<GlobalVar>(std::nullopt));
} // namespace transform
} // namespace tvm
#endif // TVM_IR_TRANSFORM_H_