670 lines
27 KiB
ReStructuredText
670 lines
27 KiB
ReStructuredText
.. 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.
|
|
|
|
.. _pass-infra:
|
|
|
|
Pass Infrastructure
|
|
===================
|
|
|
|
Both Relax and TVM IR contain a series of optimization passes which improve performance metrics
|
|
of models such as mean inference, memory footprint, or power consumption for
|
|
specific devices. There is a suite of standard optimizations as well as machine
|
|
learning-specific optimizations including constant folding, dead code
|
|
elimination, operator layout alteration, operator fusion, buffer handling, and
|
|
loop transformation, etc. Each of these passes is structured as a ir-to-ir
|
|
transformation using the analysis result collected during and/or before traversal.
|
|
|
|
However, as TVM evolves quickly, the need for a more systematic and efficient
|
|
way to manage these passes is becoming apparent. In addition, a generic
|
|
framework that manages the passes across different layers of the TVM stack (e.g.
|
|
Relax and TensorIR) paves the way for developers to quickly prototype and plug the
|
|
implemented passes into the system.
|
|
|
|
This doc describes the design of such an infra that takes the advantage of the
|
|
way production compilers are used to manage the optimization passes and the style
|
|
modern deep learning frameworks adopted to build up layers.
|
|
|
|
For example, many existing production compilers, such as GCC and LLVM, employ
|
|
pass managers to effectively manage the execution of passes. Initially managing
|
|
passes is straightforward as the number of passes is small, but mature compilers
|
|
will contain hundreds of individual passes. Often external users will want to
|
|
have custom passes correctly scheduled without having to modify a single
|
|
handcrafted pass order.
|
|
|
|
Similarly, modern deep learning frameworks, such as PyTorch, also have
|
|
the tendency to enable pass-style layer construction scheme through
|
|
`Sequential`_. With such constructs, these modern frameworks are able to
|
|
conveniently add modules/layers to their containers and build up neural
|
|
networks easily.
|
|
|
|
The design of the TVM pass infra is largely inspired by the hierarchical
|
|
pass manager used in LLVM and the block-style containers used in the popular
|
|
deep learning frameworks. The major goals of the pass infra include:
|
|
|
|
#) enabling better programmatic orchestration of optimizations. This allows
|
|
users to flexibly customize and build their own optimization pipelines.
|
|
|
|
#) providing a user-friendly way to debug optimization passes.
|
|
|
|
#) alleviating developers from manually and respectively resolving the
|
|
dependencies between passes.
|
|
|
|
#) simplifying the implementation of new passes for developers. For example, we
|
|
allow users to implement a pass in Python and let the pass infra manipulate
|
|
its execution.
|
|
|
|
The Design
|
|
----------
|
|
|
|
We focus on ease of extension for users, making it possible for users to quickly
|
|
add new passes without loss of backward compatibility. The design contains both
|
|
the backend and the frontend. The former implements the main logic of the pass
|
|
infra. The latter provides simple APIs for users to interact with, i.e.,
|
|
allowing users to quickly create their own optimization pipelines.
|
|
|
|
C++ Backend
|
|
~~~~~~~~~~~
|
|
|
|
We provide a ``PassInfo`` object to contain the basic information needed by
|
|
a pass. ``name`` is the pass name, ``opt_level`` indicates at which optimization
|
|
level the pass will be enabled, and ``required`` represents the passes that are
|
|
required to execute a certain pass (see `include/tvm/ir/transform.h`_ for
|
|
more details). For example, during registration of a pass (will be covered in
|
|
later), the pass developers can specify the name of the pass, the optimization
|
|
level it will be performed at, and/or the passes that are required.
|
|
``opt_level`` could be used to help the pass infra identify if a certain pass
|
|
needs to be executed when running under a user-provided optimization level. The
|
|
``required`` field can be used by the pass infra to resolve pass dependencies.
|
|
|
|
.. code:: c++
|
|
|
|
class PassInfoNode : public Object {
|
|
int opt_level;
|
|
ffi::String name;
|
|
bool traceable;
|
|
ffi::Array<ffi::String> required;
|
|
};
|
|
|
|
PassContext
|
|
^^^^^^^^^^^
|
|
|
|
``PassContext`` carries useful information for an optimization pass. For
|
|
example, it contains the error reporting system so optimization authors can
|
|
provide diagnostics about why an optimization fails. ``PassContext`` is also
|
|
designed to replace the old ``BuildConfig`` which was used to help users
|
|
configure the compilation options, including optimization level and
|
|
required/disabled passes, etc. For instance, we may have a configuration which
|
|
performs all passes at ``opt_level=3`` with some disabled passes using
|
|
``disabled_pass=xx`` provided by ``PassContext``. Now we could glob all passes
|
|
at ``opt_level=3`` and exclude those in the disabled pass list. ``PassContext``
|
|
also provides a way to instrument all passes. See section :ref:`pass_instrument_cpp_backend`.
|
|
|
|
This class is designed for users to conveniently write the Python ``with``
|
|
syntax to perform optimizations under a certain configuration. In addition, the
|
|
users can obtain the context that is available within a certain program scope in
|
|
a thread-safe way through ``PassContext::Current()``, since a thread-local store
|
|
``PassContextThreadLocalStore`` is used to hold the created pass context
|
|
objects. Examples will be provided later to show how we can use both the C++ and
|
|
Python APIs to create a compilation pipeline using pass context.
|
|
|
|
.. code:: c++
|
|
|
|
class PassContextNode : public Object {
|
|
public:
|
|
int opt_level{2};
|
|
ffi::Array<ffi::String> required_pass;
|
|
ffi::Array<ffi::String> disabled_pass;
|
|
mutable ffi::Optional<DiagnosticContext> diag_ctx;
|
|
ffi::Map<ffi::String, Any> config;
|
|
ffi::Array<instrument::PassInstrument> instruments;
|
|
};
|
|
|
|
class PassContext : public ObjectRef {
|
|
public:
|
|
TVM_DLL static PassContext Create();
|
|
TVM_DLL static PassContext Current();
|
|
TVM_DLL void InstrumentEnterPassContext();
|
|
TVM_DLL void InstrumentExitPassContext();
|
|
TVM_DLL bool InstrumentBeforePass(const IRModule& mod, const PassInfo& info) const;
|
|
TVM_DLL void InstrumentAfterPass(const IRModule& mod, const PassInfo& info) const;
|
|
/* Other fields are omitted. */
|
|
|
|
private:
|
|
// The entry of a pass context scope.
|
|
TVM_DLL void EnterWithScope();
|
|
// The exit of a pass context scope.
|
|
TVM_DLL void ExitWithScope();
|
|
|
|
// Classes to get the Python `with` like syntax.
|
|
friend class tvm::With<PassContext>;
|
|
};
|
|
|
|
struct PassContextThreadLocalEntry {
|
|
/*! \brief The default pass context. */
|
|
PassContext default_context;
|
|
/*! \brief The current pass context. */
|
|
std::stack<PassContext> context_stack;
|
|
PassContextThreadLocalEntry() {
|
|
default_context = PassContext(ffi::make_object<PassContextNode>());
|
|
}
|
|
};
|
|
|
|
Pass Constructs
|
|
^^^^^^^^^^^^^^^
|
|
|
|
The pass infra is designed in a hierarchical manner, and it could work at
|
|
different granularities of Relax/TensorIR programs. A pure virtual class ``PassNode`` is
|
|
introduced to serve as the base of the different optimization passes. This class
|
|
contains several virtual methods that must be implemented by the
|
|
subclasses at the level of modules, functions, or sequences of passes.
|
|
|
|
.. code:: c++
|
|
|
|
class PassNode : Object {
|
|
virtual PassInfo Info() const = 0;
|
|
virtual Module operator()(const IRModule& mod
|
|
const PassContext& pass_ctx) const = 0;
|
|
};
|
|
|
|
The functor shows how a pass must be realized, i.e. it always works on a
|
|
:py:class:`IRModule` under a certain context. All passes are designed in a ``Module`` to ``Module``
|
|
manner. Therefore, optimizations governed by the pass infra will
|
|
always update the whole module.
|
|
|
|
Several subclasses have been created to implement different types of
|
|
optimization passes, e.g., function-level passes, module-level passes, and
|
|
sequential passes. Each subclass itself could act as a pass manager. For
|
|
instance, they could collect the required passes and execute them or build
|
|
a dependency graph based on the given metadata. The full definition of them
|
|
can be found in `src/ir/transform.cc`_.
|
|
|
|
Module-Level Passes
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
Module level passes are geared mainly for global and inter-procedural
|
|
optimizations (IPO), which are similar to the module pass used in LLVM. Some
|
|
typical passes in Relax that need the global picture of a module, such as
|
|
A-normal form conversion and lambda lifting, etc., fall into this set. At this
|
|
level, users can even add and/or delete functions in a module. Note that all
|
|
passes
|
|
|
|
.. code:: c++
|
|
|
|
class ModulePassNode : PassNode {
|
|
PassInfo pass_info;
|
|
std::function<Module(Module, PassContext)> pass_func;
|
|
Module operator()(const Module& mod, const PassContext& pass_ctx) const final;
|
|
// Other members/methods are omitted
|
|
};
|
|
|
|
``pass_info`` maintains the information needed by a module-level pass.
|
|
``pass_func`` sketches the real optimization. For example, we may need to
|
|
perform dead code elimination on the module. We could implement the algorithm in
|
|
the ``pass_func`` and let it run on a module. It will then remove the dead code
|
|
including the unused functions in the module. Note that this field is designed
|
|
as a packed function, which enables the implementation of the optimization in
|
|
both C++ and Python.
|
|
|
|
Function-Level Passes
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Function-level passes are used to implement various intra-function level
|
|
optimizations for a given Relax/TensorIR module. It fetches one function at a time from
|
|
the function list of a module for optimization and yields a rewritten Relax
|
|
``Function`` or TensorIR ``PrimFunc``. Most of passes can be classified into this category, such as
|
|
common subexpression elimination and inference simplification in Relax as well as vectorization
|
|
and flattening storage in TensorIR, etc.
|
|
|
|
Note that the scope of passes at this level is either a Relax function or a TensorIR primitive function.
|
|
Therefore, we cannot add or delete a function through these passes as they are not aware of
|
|
the global information.
|
|
|
|
.. code:: c++
|
|
|
|
class FunctionPassNode : PassNode {
|
|
PassInfo pass_info;
|
|
std::function<Function(Function, Module, PassContext)> pass_func;
|
|
Module operator()(const Module& mod, const PassContext& pass_ctx) const final;
|
|
bool SkipFunction(const Function& func) const;
|
|
// Other members/methods are omitted...
|
|
};
|
|
|
|
``pass_info`` is identical to what we just described in the module pass.
|
|
``pass_func`` takes a function for optimization, it also needs a module as we
|
|
may use it for reporting errors. A function could be annotated with
|
|
"SkipOptimization" so that it will be ignored during optimization.
|
|
|
|
Sequential Passes
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
``SequentialPass`` is similar to Pytorch ``nn.Sequential`` that contains a host
|
|
of passes for execution.
|
|
|
|
.. code:: c++
|
|
|
|
class SequentialPassNode : PassNode {
|
|
PassInfo pass_info;
|
|
// Passes need to be executed.
|
|
ffi::Array<Pass> passes;
|
|
bool PassEnabled(const PassInfo& info) const;
|
|
Module operator()(const Module& mod, const PassContext& pass_ctx) const final;
|
|
};
|
|
|
|
The following code shows how individual passes in a sequential pass are invoked.
|
|
Essentially, we sequentially execute each pass in a sequential pass using the
|
|
order that they were appended to the pass list.
|
|
|
|
.. code:: c++
|
|
|
|
Module SequentialNode::operator()(const Module& module,
|
|
const PassContext& pass_ctx) const {
|
|
Module mod = module;
|
|
for (const Pass& pass : passes) {
|
|
TVM_FFI_ICHECK(pass.defined()) << "Found undefined pass for optimization.";
|
|
const PassInfo& pass_info = pass->Info();
|
|
if (!PassEnabled(pass_info)) continue;
|
|
for (const auto& it : pass_info->required) {
|
|
mod = GetPass(it)(std::move(mod), pass_ctx);
|
|
}
|
|
mod = pass(mod, pass_ctx);
|
|
}
|
|
return mod;
|
|
}
|
|
|
|
Upon the invocation of a pass, we first check if this pass is enabled. This is
|
|
done by first checking if the pass is explicitly disabled by a user, followed by
|
|
inspecting if it is specified as a required pass by the user. If it is still
|
|
undetermined whether this pass is enabled, its ``opt_level`` will be checked.
|
|
This pass will be enabled and therefore executed only when its optimization
|
|
level is not less than the configured optimization level in the pass context.
|
|
|
|
To execute the pass, we need first to retrieve the registered pass in the TVM
|
|
packed function registry using the pass name. This is possible because every
|
|
pass is registered with an API endpoint as we will show later.
|
|
|
|
.. code:: c++
|
|
|
|
Pass GetPass(const std::string& pass_name) {
|
|
std::string fpass_name = "relax.transform." + pass_name;
|
|
const std::optional<tvm::ffi::Function> f = tvm::ffi::Function::GetGlobal(fpass_name);
|
|
TVM_FFI_ICHECK(f.has_value()) << "Cannot find " << fpass_name
|
|
<< "to create the pass " << pass_name;
|
|
return (*f)();
|
|
}
|
|
|
|
Some helper functions are provided to create each type of these aforementioned
|
|
passes. These helpers are also exposed to the Python frontend for users to
|
|
favorably use Python APIs to create a specific pass object.
|
|
|
|
.. code:: c++
|
|
|
|
Pass CreateFunctionPass(
|
|
std::function<Function(Function, IRModule, PassContext)> pass_func,
|
|
int opt_level,
|
|
ffi::String name,
|
|
ffi::Array<ffi::String> required,
|
|
bool traceable = false);
|
|
|
|
Pass CreatePrimFuncPass(
|
|
std::function<PrimFunc(PrimFunc, IRModule, PassContext)> pass_func,
|
|
int opt_level,
|
|
ffi::String name,
|
|
ffi::Array<ffi::String> required,
|
|
bool traceable = false);
|
|
|
|
Pass CreateModulePass(
|
|
std::function<IRModule(IRModule, PassContext)> pass_func,
|
|
int opt_level,
|
|
ffi::String name,
|
|
ffi::Array<ffi::String> required,
|
|
bool traceable = false);
|
|
|
|
Pass Sequential(tvm::ffi::Array<Pass> passes, PassInfo pass_info);
|
|
|
|
Pass Registration
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
We've covered the concept of different level of passes and the context used for
|
|
compilation. It would be interesting to see how easily users can register
|
|
a pass. Let's take const folding as an example. This pass has already been
|
|
implemented to fold constants in a Relax function (found in
|
|
`src/relax/transform/fold_constant.cc`_).
|
|
|
|
An API was provided to perform the ``Expr`` to ``Expr`` transformation.
|
|
|
|
.. code:: c++
|
|
|
|
Expr FoldConstant(const Expr& expr);
|
|
|
|
In order to register this pass to the pass infra, we first need to decide at
|
|
which level this pass will be performed. As const folding happens on individual
|
|
functions, we should intuitively create a ``FunctionPass`` for it through
|
|
``CreateFunctionPass``. The ``pass_func`` is returned as a packed function that
|
|
invokes the ``Expr`` to ``Expr`` API on each function in a `IRModule`. ``{}``
|
|
indicates that no prerequisite is required for this pass. Otherwise, the pass
|
|
developer has to identify and list them.
|
|
|
|
Meanwhile, a pass API endpoint is registered with the name
|
|
``"relax.transform.FoldConstant"``. This pass, therefore, becomes an entry in the
|
|
registry that can be accessed by both C++ (e.g. the ``GetPass`` above) and
|
|
Python when needed.
|
|
|
|
.. code:: c++
|
|
|
|
namespace transform {
|
|
|
|
Pass FoldConstant() {
|
|
auto pass_func =
|
|
[=](Function f, IRModule m, PassContext pc) { return ConstantFolder::Fold(f, m); };
|
|
return CreateFunctionPass(pass_func, 0, "FoldConstant", {});
|
|
}
|
|
|
|
TVM_FFI_STATIC_INIT_BLOCK() {
|
|
namespace refl = tvm::ffi::reflection;
|
|
refl::GlobalDef().def("relax.transform.FoldConstant", FoldConstant);
|
|
}
|
|
|
|
} // namespace transform
|
|
|
|
To allow other C++ modules to apply this pass, we declare a free function in
|
|
`include/tvm/relax/transform.h`_ as the following:
|
|
|
|
.. code:: c++
|
|
|
|
TVM_DLL Pass FoldConstant();
|
|
|
|
.. _pass_instrument_cpp_backend:
|
|
|
|
Pass Instrument
|
|
^^^^^^^^^^^^^^^
|
|
|
|
Pass Instrument is a mechanism to analyze the pass itself. For example,
|
|
we can use the infrastructure to know how much time and memory a pass requires
|
|
or how a pass can transform the IR module.
|
|
|
|
We introduce four instrument points in the life-cycle of ``PassContext``.
|
|
|
|
.. code:: c++
|
|
|
|
TVM_DLL void InstrumentEnterPassContext();
|
|
TVM_DLL void InstrumentExitPassContext();
|
|
TVM_DLL bool InstrumentBeforePass(const IRModule& mod, const PassInfo& info) const;
|
|
TVM_DLL void InstrumentAfterPass(const IRModule& mod, const PassInfo& info) const;
|
|
|
|
``InstrumentEnterPassContext`` is called immediately when entering the scope
|
|
of the ``PassContext`` instance.
|
|
|
|
``InstrumentExitPassContext`` is called when leaving the scope of ``PassContext``,
|
|
or exceptions occur during the execution of passes.
|
|
This method is also called when instruments is being overridden by ``override_instruments`` in :py:class:`tvm.transform.PassContext`.
|
|
See :ref:`pass_instrument_overriden`.
|
|
|
|
``InstrumentBeforePass`` is called before execution.
|
|
``InstrumentAfterPass`` is called after execution if the pass should be run. The behavior is like:
|
|
|
|
.. code:: c++
|
|
|
|
if (pass_ctx.InstrumentBeforePass(ir_module, pass_info)) {
|
|
new_ir_module = run_pass(ir_module, pass_ctx);
|
|
pass_ctx.InstrumentAfterPass(new_ir_module, pass_info);
|
|
return new_ir_module;
|
|
}
|
|
|
|
The ``PassInstrument`` interface allow you to run arbitrary code inside above four methods.
|
|
Multiple ``PassInstrument`` instances can be registed into a single
|
|
``PassContext``. ``PassInstrument`` instances are called sequentially in the order of
|
|
``instruments`` argument passed to ``PassContext``.
|
|
|
|
``PassInstrument`` provides following interfaces:
|
|
|
|
.. code:: c++
|
|
|
|
namespace instrument {
|
|
|
|
class PassInstrumentNode : public Object {
|
|
public:
|
|
ffi::String name;
|
|
virtual void EnterPassContext() const = 0;
|
|
virtual void ExitPassContext() const = 0;
|
|
virtual bool ShouldRun(const IRModule& mod, const transform::PassInfo& info) const = 0;
|
|
virtual void RunBeforePass(const IRModule& mod, const transform::PassInfo& info) const = 0;
|
|
virtual void RunAfterPass(const IRModule& mod, const transform::PassInfo& info) const = 0;
|
|
/* Other fields are omitted. */
|
|
};
|
|
|
|
class PassInstrument : public ObjectRef {
|
|
public:
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PassInstrument, ObjectRef, PassInstrumentNode);
|
|
};
|
|
|
|
} // namespace instrument
|
|
|
|
Python frontend are provided to implement ``PassInstrument`` quickly. See :ref:`pass_instrument_py_frontend`.
|
|
|
|
Within a ``PassContext``, the call sequence of a ``PassInstrument`` instance is like:
|
|
|
|
::
|
|
|
|
with PassContext(instruments=[pi]) # pi = a PassInstrument implementation.
|
|
pi.EnterPassContext()
|
|
|
|
if pi.ShouldRun(Pass1):
|
|
pi.RunBeforePass()
|
|
Pass1()
|
|
pi.RunAfterPass()
|
|
|
|
if pi.ShouldRun(Pass2):
|
|
pi.RunBeforePass()
|
|
Pass2()
|
|
pi.RunAfterPass()
|
|
|
|
pi.ExitPassContext()
|
|
|
|
Here is a brief introduction of relations between ``PassInstrument`` interfaces
|
|
and ``PassContext`` methods. See (`src/ir/transform.cc`_) for more details.
|
|
|
|
- ``InstrumentEnterPassContext``
|
|
|
|
* ``EnterPassContext()`` is executed in the order of ``instruments`` passed to the ``PassContext``.
|
|
* When an exception raises, ``PassContext`` disable the pass instrumentation
|
|
by clearing all registered ``PassInstrument`` instances.
|
|
* Then ``PassContext`` execute ``ExitPassContext()`` method of each ``PassInstrument``
|
|
instances which successfully finished ``EnterPassContext()``
|
|
* For example, if ``PassInstrument`` A, B, and C are registered to a ``PassContext``
|
|
and A finished ``EnterPassContext()`` while B throws an exception, then C
|
|
is never executed; ``ExitPassContext()`` of A is executed.
|
|
|
|
- ``InstrumentExitPassContext``
|
|
|
|
* ``ExitPassContext()`` of each ``PassInstrument`` instances are executed in
|
|
the order of ``instruments`` passed to the ``PassContext``.
|
|
* While an exception occurs, ``instruments`` is cleared.
|
|
* ``PassInstrument`` Instances registered after the one throwing exceptions do not execute ``ExitPassContext``.
|
|
|
|
- ``InstrumentBeforePass``
|
|
|
|
* ``ShouldRun`` is executed if the pass is not listed as a required pass.
|
|
* ``RunBeforePass`` is executed in the order of ``instruments`` if the pass is not blocked by ``ShouldRun``.
|
|
* Note that ``InstrumentBeforePass`` returns a boolean indicating whether or not the pass should be run.
|
|
* When an exception occur, it is thrown immediately.
|
|
We rely on Python Context Manager to exit ``PassContext`` safely
|
|
(meaning ``ExitPassContext`` of each instruments will be run. For C++, please refer to `include/tvm/support/with.h`_.)
|
|
|
|
- ``InstrumentAfterPass``
|
|
|
|
* ``RunAfterPass`` is executed in the order of ``instruments`` passed to the ``PassContext``.
|
|
* When an exception occur, it is thrown immediately.
|
|
We rely on Python Context Manager or ``With`` class(`include/tvm/support/with.h`_) to exit ``PassContext`` safely
|
|
|
|
Built-in Instrument
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
There are several built-in instruments.
|
|
|
|
- PassTimingInstrument (see `src/ir/instrument.cc`_)
|
|
|
|
* Profile the execution time of passes.
|
|
|
|
- PrintBeforeAll (see `python/tvm/ir/instrument.py`_)
|
|
|
|
* Print the IR module and pass info before each pass executes.
|
|
|
|
- PrintAfterAll (see `python/tvm/ir/instrument.py`_)
|
|
|
|
* Print the IR module and pass info after each pass executes.
|
|
|
|
- PassPrintingInstrument (see `python/tvm/ir/instrument.py`_)
|
|
|
|
* Selectively print the IR module before or after specific named passes.
|
|
|
|
- DumpIR (see `python/tvm/ir/instrument.py`_)
|
|
|
|
* Dump the IR module to files after each pass executes.
|
|
|
|
Python Frontend
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Only some simple APIs are needed for the frontend side. For example, we can
|
|
provide users the following APIs to create and execute a pass (full
|
|
implementation is provided in `python/tvm/relax/transform/transform.py`_ and
|
|
`python/tvm/ir/transform.py`_). The backend
|
|
receives the information and decides which function it should use to create
|
|
a Pass object.
|
|
|
|
PassContext
|
|
^^^^^^^^^^^
|
|
|
|
Python frontend provides a wrapper for the ``PassContext`` to enable the
|
|
``with`` syntax by overriding ``__enter__`` and ``__exit__``. A ``current``
|
|
static method is offered for users to get the context that is in use under
|
|
a certain scope.
|
|
|
|
.. code:: python
|
|
|
|
@tvm_ffi.register_object("transform.PassContext")
|
|
class PassContext(tvm.runtime.Object):
|
|
def __enter__(self):
|
|
_transform.EnterPassContext(self)
|
|
return self
|
|
|
|
def __exit__(self, ptype, value, trace, config):
|
|
_transform.ExitPassContext(self)
|
|
|
|
@staticmethod
|
|
def current():
|
|
"""Return the current pass context."""
|
|
return _transform.GetCurrentPassContext()
|
|
|
|
A ``PassContext`` is used to configure the compilation options, including the
|
|
optimization level and required/disabled passes. It can also take a dictionary
|
|
of configs so that different passes can conveniently fetch the passed data, such
|
|
as fallback device info and step/depth for loop unrolling, etc. In order to
|
|
enable fetching the required config, the key must be registered through
|
|
``TVM_REGISTER_PASS_CONFIG_OPTION``. For example, the following is used by the
|
|
loop unrolling pass
|
|
|
|
.. code:: c++
|
|
|
|
TVM_REGISTER_PASS_CONFIG_OPTION("tirx.UnrollLoop", UnrollLoopConfig);
|
|
|
|
Please refer to `src/tirx/transform/unroll_loop.cc`_ for more details.
|
|
|
|
.. _pass_instrument_py_frontend:
|
|
|
|
Pass Instrument
|
|
^^^^^^^^^^^^^^^
|
|
|
|
One can implement a ``PassInstrument`` by using the ``pass_instrument``
|
|
decorator(`python/tvm/ir/instrument.py`_) on a class implementing following methods.
|
|
Note that it is recommended to use the ``pass_instrument`` decorator to implement
|
|
``PassInstrument``, instead of overriding or subclassing.
|
|
|
|
- ``enter_pass_ctx``
|
|
|
|
* This method is run when entering ``PassContext``.
|
|
|
|
- ``exit_pass_ctx``
|
|
|
|
* This method is run when exiting ``PassContext``.
|
|
|
|
- ``should_run``
|
|
|
|
* This method is run before a pass is executed, returning a boolean
|
|
indicating whether or not the pass should be run.
|
|
|
|
- ``run_before_pass``
|
|
|
|
* If a pass should be run, this method is run just before pass execution.
|
|
|
|
- ``run_after_pass``
|
|
|
|
* This method is run right after a pass has been executed.
|
|
|
|
``PassInstrument`` instances can be registered through ``instruments`` argument in
|
|
:py:class:`tvm.transform.PassContext`.
|
|
|
|
See `python/tvm/ir/instrument.py`_ for examples of how to implement ``PassInstrument`` with Python APIs.
|
|
|
|
.. _pass_instrument_overriden:
|
|
|
|
Override Instruments in Current PassContext
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
``override_instruments`` method is provided to override the ``instruments`` of current ``PassContext``.
|
|
For example, if passes are run without explicitly creating a new ``PassContext``,
|
|
one can still register ``PassInstrument`` into the global ``PassContext`` by:
|
|
|
|
.. code:: python
|
|
|
|
cur_pass_ctx = tvm.transform.PassContext.current()
|
|
# override PassInstrument instances
|
|
cur_pass_ctx.override_instruments([pass_inst])
|
|
mod = pass_seq(mod)
|
|
result = pass_inst.get_result()
|
|
|
|
Note that when ``override_instruments`` is called, the ``exit_pass_ctx`` method of
|
|
old ``PassInstrument`` instances are called. Then the ``enter_pass_ctx`` method of
|
|
new ``PassInstrument`` are called.
|
|
|
|
.. _Sequential: https://pytorch.org/docs/stable/nn.html?highlight=sequential#torch.nn.Sequential
|
|
|
|
.. _Block: https://pytorch.org/docs/stable/generated/torch.nn.Module.html
|
|
|
|
.. _include/tvm/ir/transform.h: https://github.com/apache/tvm/blob/main/include/tvm/ir/transform.h
|
|
|
|
.. _include/tvm/support/with.h: https://github.com/apache/tvm/blob/main/include/tvm/support/with.h
|
|
|
|
.. _src/relax/ir/transform.cc: https://github.com/apache/tvm/blob/main/src/relax/ir/transform.cc
|
|
|
|
.. _src/ir/transform.cc: https://github.com/apache/tvm/blob/main/src/ir/transform.cc
|
|
|
|
.. _src/ir/instrument.cc: https://github.com/apache/tvm/blob/main/src/ir/instrument.cc
|
|
|
|
.. _src/relax/transform/fold_constant.cc: https://github.com/apache/tvm/blob/main/src/relax/transform/fold_constant.cc
|
|
|
|
.. _python/tvm/relax/transform/transform.py: https://github.com/apache/tvm/blob/main/python/tvm/relax/transform/transform.py
|
|
|
|
.. _include/tvm/relax/transform.h: https://github.com/apache/tvm/blob/main/include/tvm/relax/transform.h
|
|
|
|
.. _python/tvm/ir/transform.py: https://github.com/apache/tvm/blob/main/python/tvm/ir/transform.py
|
|
|
|
.. _python/tvm/ir/instrument.py: https://github.com/apache/tvm/blob/main/python/tvm/ir/instrument.py
|
|
|
|
.. _src/tirx/transform/unroll_loop.cc: https://github.com/apache/tvm/blob/main/src/tirx/transform/unroll_loop.cc
|
|
|
|
.. _use pass infra: https://github.com/apache/tvm/blob/main/docs/how_to/tutorials/customize_opt.py
|