chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
doxygen
modules
+5
View File
@@ -0,0 +1,5 @@
[rstcheck]
report_level = warning
ignore_directives = automodule, autosummary, currentmodule, toctree, ifconfig, tab-set, collapse, tabs, dropdown, doxygenclass, doxygenfunction, doxygenstruct, doxygennamespace, figure, code-block, graphviz
ignore_roles = ref, cpp:class, cpp:func, py:func, c:macro, doc, any
ignore_languages = cpp, python
+2342
View File
File diff suppressed because it is too large Load Diff
+250
View File
@@ -0,0 +1,250 @@
# 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.
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python3 -m sphinx
PAPER =
BUILDDIR = _build
STAGINGDIR = _staging
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(PWD)/$(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext staging
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)
rm -rf $(STAGINGDIR)
# TODO(Lunderberg): Remove these lines once the CI steps have
# propagated.
# Remove folders that have since been relocated into
# $(STAGINGDIR). This allows `task_python_docs.sh` to
# run, even if a commit that predates $(STAGINGDIR) was
# previously run on that node.
rm -rf gen_modules
rm -rf user_tutorials
rm -rf tutorials
staging:
# Prepare the staging directory. Sphinx gallery automatically
# writes new .rst files into the current directory. This can
# cause issues when switching branches. By sequestering the
# auto-generated files into the staging directory, they can be
# removed without knowing the exact directory.
mkdir -p $(STAGINGDIR)
# Remove any symlinks that currently exist
find $(STAGINGDIR) -type l -exec rm {} \;
# Reproduce the directory structure
find . \
-path ./$(BUILDDIR) -prune -o -path ./$(STAGINGDIR) -prune -o \
-name "*.rst" \
-printf "$(STAGINGDIR)/%h\n" \
| sort | uniq | xargs mkdir -p
# Symlink all .rst files into the staging directory
find . \
-path ./$(BUILDDIR) -prune -o -path ./$(STAGINGDIR) -prune -o \
-name "*.rst" \
-exec ln -s $(PWD)/{} $(STAGINGDIR)/{} \;
ln -s $(PWD)/conf.py $(STAGINGDIR)/conf.py
ln -s $(PWD)/_static $(STAGINGDIR)/_static
html: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
htmldepoly: html
python3 $(PWD)/download_3rdparty_embeds.py -v
@echo "Replaced external URLs with local files."
dirhtml: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc"
applehelp: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/rabit"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit"
@echo "# devhelp"
epub: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(PWD)/$(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml: staging
cd $(STAGINGDIR) && $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(PWD)/$(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+173
View File
@@ -0,0 +1,173 @@
<!--- 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. -->
# TVM Documentation
This folder contains the source of TVM's documentation, hosted at https://tvm.apache.org/docs
## Build Locally
### With Docker (recommended)
1. Build TVM and the docs inside the [tlcpack/ci-gpu image](https://hub.docker.com/r/tlcpack/ci-gpu) using the [`ci.py`](../tests/scripts/ci.py) script.
```bash
# If this runs into errors, try cleaning your 'build' directory
python tests/scripts/ci.py docs
# See other doc building options
python tests/scripts/ci.py docs --help
```
2. Serve the docs and visit http://localhost:8000 in your browser
```bash
# Run an HTTP server you can visit to view the docs in your browser
python tests/scripts/ci.py serve-docs
```
### Native
1. [Build TVM](https://tvm.apache.org/docs/install/from_source.html) first in the repo root folder, then make it importable:
```bash
export TVM_HOME=/path-to-tvm
export TVM_LIBRARY_PATH=$TVM_HOME/build
pip install --target=$TVM_HOME/python $TVM_HOME/3rdparty/tvm-ffi
export PYTHONPATH=$TVM_HOME/python:$PYTHONPATH
```
`docs/conf.py` unconditionally imports `tvm` at startup, so the build will
fail immediately if TVM is not importable.
2. Install dependencies
```bash
# Pillow on Ubuntu may require libjpeg-dev from apt
./docker/bash.sh ci_gpu -c \
'python3 -m pip install --quiet sphinx-book-theme==1.1.4 && python3 -m pip freeze' > frozen-requirements.txt
pip install -r frozen-requirements.txt
```
3. Generate the docs
```bash
# TVM_TUTORIAL_EXEC_PATTERN=none skips the tutorial execution to the build
# work on most environments (e.g. MacOS).
export TVM_TUTORIAL_EXEC_PATTERN=none
cd docs
make html
```
4. Run an HTTP server and visit http://localhost:8000 in your browser
```bash
cd _build/html && python3 -m http.server
```
## Only Execute Specified Tutorials
The document build process will execute all the tutorials in the sphinx gallery.
This will cause failure in some cases when certain machines do not have necessary
environment. You can set `TVM_TUTORIAL_EXEC_PATTERN` to only execute
the path that matches the regular expression pattern.
For example, to only build tutorials under `/get_started/tutorials`, run
```bash
python tests/scripts/ci.py docs --tutorial-pattern=/get_started/tutorials
```
To only build one specific file, do
```bash
# The slash \ is used to get . in regular expression
python tests/scripts/ci.py docs --tutorial-pattern=file_name\.py
```
## Helper Scripts
The following script mirrors the CI docs pipeline: it runs a sphinx pre-check
(when not running locally) and then performs a full `make htmldepoly` build,
including tutorial execution. You will need a GPU CI environment.
```bash
tests/scripts/task_python_docs.sh
```
To build docs locally without executing tutorials (fastest local iteration):
```bash
cd docs && TVM_TUTORIAL_EXEC_PATTERN=none make html
```
Note: the sphinx pre-check (warning validation) only runs in CI (`IS_LOCAL=0`).
`python tests/scripts/ci.py docs` always sets `IS_LOCAL=1` and skips the
pre-check regardless of other flags.
To run the full build including tutorial executions:
```bash
python tests/scripts/ci.py docs --full
```
## Define the Order of Tutorials
You can define the order of tutorials with `subsection_order` and
`within_subsection_order` in [`conf.py`](conf.py).
By default, the tutorials within one subsection are sorted by filename.
## Google Colab Integration
All the TVM tutorials can be opened and used interactively in Google Colab by
clicking the button at the top of the page. To do this, `sphinx-gallery` builds
`.ipynb` files from each tutorial, which are automatically deployed to the
[apache/tvm-site](https://github.com/apache/tvm-site/tree/asf-site) repo's
`asf-site` branch by [@tvm-bot](https://github.com/tvm-bot).
To make sure your tutorial runs correctly on Colab, any non-Python parts of
the tutorial (e.g. dependency installations) should be prefixed by an
[IPython magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html).
These will not be included in the built `HTML` file. For example, to install
Pytorch in your tutorial, add a ReStructured Text block like the following:
```python
######################################################################
# To run this tutorial, we must install PyTorch:
#
# .. code-block:: bash
#
# %%shell
# pip install torch
#
```
### Interactive Bash Scripts
In stock IPython, the `%%bash` magic command should be used to run shell
commands. However, this command does not give real-time output - the
tutorial's user will not see any output until the entire cell finishes
running. When running commands that take several minutes (e.g. installing
dependencies), this is annoying.
Luckily, Google Colab has the `%%shell` magic command that does the same
thing as `%%bash`, but gives output in real time. This command is specific
to Colab, and its [source code](https://github.com/googlecolab/colabtools)
is public. Thus, `%%shell` should be used instead of `%%bash` when writing
TVM tutorials.
+2
View File
@@ -0,0 +1,2 @@
The logo file in this repo is an exception due to the need of sphinx.
By default we avoid to put large binary blobs into this repo.
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

+306
View File
@@ -0,0 +1,306 @@
.. 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.
.. _codegen-arch:
Code Generation
===============
Code generation is the final stage of the TVM compilation pipeline — it translates TIR
``PrimFunc``\ s into executable code for a target device. This document explains how TIR
functions become native CPU instructions, GPU kernels, or source code strings, covering the
target dispatch mechanism, the two codegen families (LLVM and Source), and the runtime module
system that wraps the generated code.
Where Codegen Fits
------------------
When a user calls ``tvm.compile()``, the compilation proceeds in two phases:
1. **Relax phase**: the Relax pipeline optimizes and fuses the computational graph, then
``VMCodeGen`` translates Relax functions into VM bytecode (see :ref:`relax-vm-arch`).
2. **TIR phase**: TIR ``PrimFunc``\ s (the actual compute kernels) are compiled to native code.
The TIR phase is handled internally by ``tirx.build()`` (called from ``relax.build()``).
It performs these steps:
.. code-block:: text
TIR PrimFuncs (in IRModule)
▼ TIR pipeline ← lowering passes (flatten buffers, lower intrinsics, etc.)
TIR PrimFuncs (lowered)
▼ split_host_device_mods() ← separate host and device functions
Host IRModule + Device IRModule(s)
│ │
▼ ▼
codegen_build() codegen_build() ← target-specific code generation
│ │
▼ ▼
Host Module Device Module(s)
│ │
▼ import_module() │
Host Module ◄─────────────┘ ← device modules imported into host
▼ (returned to relax.build for linking with VM bytecode)
Target Dispatch
---------------
The core dispatch logic lives in ``codegen::Build()`` (``src/target/codegen.cc``), which is
called from the Python-side ``codegen_build()`` in ``tirx/build.py``. It selects the correct
backend based on the ``Target`` object:
.. code-block:: cpp
ffi::Module Build(IRModule mod, Target target) {
std::string build_f_name = "target.build." + target->kind->name;
const auto bf = tvm::ffi::Function::GetGlobal(build_f_name);
return (*bf)(mod, target).cast<ffi::Module>();
}
Each backend registers its build function via FFI:
.. list-table::
:header-rows: 1
:widths: 25 30 45
* - FFI Key
- Backend
- Codegen Class
* - ``target.build.llvm``
- CPU (x86, ARM, etc.)
- ``CodeGenCPU`` (→ LLVM IR → machine code)
* - ``target.build.cuda``
- NVIDIA GPU
- ``CodeGenCUDA`` (→ CUDA C → PTX/cubin)
* - ``target.build.rocm``
- AMD GPU
- ``CodeGenAMDGPU`` (→ LLVM IR → AMDGPU ISA)
* - ``target.build.nvptx``
- NVIDIA PTX
- ``CodeGenNVPTX`` (→ LLVM IR → PTX)
* - ``target.build.metal``
- Apple GPU
- ``CodeGenMetal`` (→ Metal Shading Language)
* - ``target.build.opencl``
- OpenCL devices
- ``CodeGenOpenCL`` (→ OpenCL C)
* - ``target.build.vulkan``
- Vulkan devices
- ``CodeGenSPIRV`` (→ SPIR-V binary)
* - ``target.build.webgpu``
- WebGPU
- ``CodeGenWebGPU`` (→ WGSL)
* - ``target.build.c``
- C host code
- ``CodeGenCHost`` (→ C source)
Two Codegen Families
--------------------
TVM has two families of code generators, corresponding to two fundamentally different strategies
for producing executable code:
.. code-block:: text
LLVM Family Source Family
────────── ─────────────
TIR → LLVM IR → machine code TIR → source string → external compiler
(in-process, JIT or AOT) (CUDA C, OpenCL C, Metal, WGSL)
LLVM family
~~~~~~~~~~~
``CodeGenLLVM`` (``src/target/llvm/codegen_llvm.h``) translates TIR directly to LLVM IR using
the LLVM C++ API. The generated ``llvm::Module`` is then compiled to native code by LLVM's
backend (x86, ARM, NVPTX, AMDGPU, etc.).
**Inheritance**:
.. code-block:: text
CodeGenLLVM (base)
├── CodeGenCPU ← x86, ARM (target.build.llvm)
│ └── CodeGenHexagon
├── CodeGenNVPTX ← NVIDIA PTX via LLVM (target.build.nvptx)
└── CodeGenAMDGPU ← AMD GPU via LLVM (target.build.rocm)
``CodeGenLLVM`` inherits from both ``ExprFunctor<llvm::Value*(const Expr&)>`` and
``StmtFunctor<void(const Stmt&)>``. Each TIR node type has a corresponding visitor:
- **Expressions** (``VisitExpr_``) convert TIR expressions to LLVM ``Value``\ s:
arithmetic ops → LLVM binary instructions, ``BufferLoad`` → load with pointer arithmetic,
``Cast`` → LLVM type conversions, ``Call`` → intrinsic or extern function calls.
- **Statements** (``VisitStmt_``) emit LLVM IR side effects:
``BufferStore`` → store instructions, ``For`` → loop basic blocks with branches,
``IfThenElse`` → conditional branches, ``AllocBuffer`` → stack or heap allocation.
The key methods on ``CodeGenLLVM`` are:
- ``Create(LLVMTarget*)`` — factory that returns a target-specific subclass.
- ``Init(...)`` — set up the LLVM context, module, and builder.
- ``DeclareFunction(gvar, f)`` / ``AddFunction(gvar, f)`` — forward-declare then compile a
``PrimFunc`` to LLVM IR.
- ``Finish()`` — return the completed ``llvm::Module``.
Source family
~~~~~~~~~~~~~
``CodeGenC`` (``src/target/source/codegen_c.h``) generates C-like source code as text. Each
target subclass overrides methods to emit target-specific syntax.
**Inheritance**:
.. code-block:: text
CodeGenC (base)
├── CodeGenCUDA ← CUDA C (target.build.cuda)
├── CodeGenOpenCL ← OpenCL C (target.build.opencl)
├── CodeGenMetal ← Metal Shading Language (target.build.metal)
├── CodeGenWebGPU ← WGSL (target.build.webgpu)
└── CodeGenCHost ← C host code (target.build.c)
``CodeGenC`` also uses the visitor pattern (``ExprFunctor`` and ``StmtFunctor``), but outputs to
``std::ostream`` instead of constructing LLVM IR. Subclasses override target-specific methods:
- ``PrintStorageScope(scope, os)`` — emit memory qualifiers (e.g., ``__shared__`` for CUDA,
``__local`` for OpenCL).
- ``BindThreadIndex(iv)`` — emit thread index bindings (e.g., ``threadIdx.x``, ``blockIdx.y``).
- ``PrintType(dtype, os)`` — emit target-specific type names (e.g., ``half`` for float16).
- ``PrintVecBinaryOp(...)`` — emit vectorized operations in target syntax.
For CUDA, the build flow (``BuildCUDA`` in ``src/target/opt/build_cuda_on.cc``) is:
1. ``CodeGenCUDA`` generates CUDA C source.
2. An optional post-processing callback (``tvm_callback_cuda_postproc``) transforms the source.
3. A Python callback (``tvm_callback_cuda_compile``) compiles the source to PTX or cubin via
NVRTC or NVCC.
4. The result is wrapped in a ``CUDAModule``.
Design choice
~~~~~~~~~~~~~
Why two families?
- **LLVM family** produces higher-quality code — LLVM applies its own optimization passes
(instruction selection, register allocation, vectorization). Best for CPU targets where TVM
has full control over the compilation.
- **Source family** is more portable — it generates human-readable source that can be compiled
by vendor toolchains (NVCC, Metal compiler, etc.). This is necessary for GPU targets where
the vendor compiler handles device-specific optimizations and the runtime compilation model
(e.g., NVRTC for CUDA, runtime shader compilation for Metal/OpenCL).
Host/Device Split
-----------------
When compiling for GPU targets, TIR functions are split into two categories:
- **Host functions** — run on the CPU. They set up kernel launch parameters (grid/block
dimensions), allocate memory, and invoke device kernels. Compiled with ``target.build.llvm``
or ``target.build.c``.
- **Device functions** — the actual compute kernels that run on the GPU. Compiled with the
target-specific codegen (``target.build.cuda``, etc.).
``split_host_device_mods()`` (``python/tvm/tirx/build.py``) separates functions by their
``target`` attribute: functions whose target kind is ``"llvm"`` or ``"c"`` go to the host
module; all others go to device modules grouped by target.
After compilation, device modules are imported into the host module via ``import_module()``,
forming a module tree. At runtime, the host module dispatches to the imported device module
when a device kernel is called.
Runtime Modules
---------------
Each codegen produces a ``runtime.Module`` — the container that holds the generated code and
exposes it as callable ``PackedFunc``\ s.
.. list-table::
:header-rows: 1
:widths: 20 35 45
* - Module Type
- How Code Is Stored
- How Code Is Executed
* - ``LLVMModule``
- LLVM IR (in-memory ``llvm::Module``)
- JIT-compiled on first call (MCJIT or ORC). Function pointers cached for subsequent calls.
* - ``CUDAModule``
- PTX or cubin binary
- Loaded via CUDA driver API (``cuModuleLoad``). Kernels launched via ``cuLaunchKernel``.
* - ``CSourceModule``
- C source string
- Not directly executable. Used as a build artifact for AOT compilation.
* - ``DeviceSourceModule``
- Device source string (OpenCL C, Metal, WGSL)
- Compiled at runtime by the device driver (e.g., ``clCreateProgramWithSource``).
All module types implement the same interface: ``GetFunction(name)`` returns a ``PackedFunc``
that can be called from Python or C++. The VM and other runtime components use this interface
to invoke compiled kernels without knowing which backend produced them.
The module tree is serializable via ``export_library()``, which packs the host module and all
imported device modules into a single shared library (``.so`` / ``.dll`` / ``.dylib``) or
a tar archive for deployment.
Source Code Map
---------------
.. list-table::
:header-rows: 1
:widths: 50 50
* - Path
- Contents
* - ``python/tvm/tirx/build.py``
- ``tirx.build()``: TIR compilation entry, host/device split, module linking
* - ``src/target/codegen.cc``
- ``codegen::Build()``: target dispatch via ``"target.build.<kind>"``
* - ``src/target/llvm/codegen_llvm.h``
- ``CodeGenLLVM``: TIR → LLVM IR base class
* - ``src/target/llvm/codegen_cpu.h``
- ``CodeGenCPU``: CPU-specific LLVM codegen (x86, ARM)
* - ``src/target/llvm/codegen_nvptx.cc``
- ``CodeGenNVPTX``: NVIDIA PTX via LLVM
* - ``src/target/llvm/codegen_amdgpu.cc``
- ``CodeGenAMDGPU``: AMD GPU via LLVM
* - ``src/target/llvm/llvm_module.cc``
- ``LLVMModuleNode``: runtime module with JIT compilation
* - ``src/target/source/codegen_c.h``
- ``CodeGenC``: TIR → C-like source base class
* - ``src/target/source/codegen_cuda.h``
- ``CodeGenCUDA``: TIR → CUDA C
* - ``src/target/source/codegen_opencl.h``
- ``CodeGenOpenCL``: TIR → OpenCL C
* - ``src/target/source/codegen_metal.h``
- ``CodeGenMetal``: TIR → Metal Shading Language
* - ``src/target/source/codegen_c_host.h``
- ``CodeGenCHost``: TIR → C host code
* - ``src/target/opt/build_cuda_on.cc``
- ``BuildCUDA``: CUDA build flow (codegen → compile → module)
* - ``src/target/spirv/codegen_spirv.h``
- ``CodeGenSPIRV``: TIR → SPIR-V for Vulkan
* - ``src/target/source/codegen_webgpu.h``
- ``CodeGenWebGPU``: TIR → WGSL
+245
View File
@@ -0,0 +1,245 @@
.. 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.
.. _tvm-target-specific-overview:
Device/Target Interactions
==========================
This documented is intended for developers interested in understanding
how the TVM framework interacts with specific device APIs, or who
may want to implement support for a new API or new hardware.
There are three main aspects that must be implemented for any new
runtime environment.
* The :ref:`DeviceAPI <tvm-target-specific-device-api>` class gives a
handle to a specific device, and the API used to interact with it.
It defines a common interface for querying device parameters
(e.g. memory available, number of threads, etc.) and for performing
simple actions (e.g. copying memory from the host, or between
buffers on the device).
* The :ref:`Target <tvm-target-specific-target>` class contains a
description of the device on which a function will run. It is
exposed both to the target code generators and to the optimization
passes.
* The :ref:`target code generators <tvm-target-specific-codegen>`
construct a :ref:`Module <tvm-runtime-system-module>` consisting of
one or more :ref:`PackedFunc <tvm-runtime-system-packed-func>`, from
an IRModule.
.. _tvm-target-specific-device-api:
DeviceAPI
---------
The ``DeviceAPI`` represents a handle to a specific hardware device
API. (e.g. ``CUDADeviceAPI`` handles all interactions through the
CUDA framework.) Most ``DeviceAPI`` methods accept a ``device_id``
parameter to specify which device should be accessed. In Python,
these are typically accessed using the :py:func:`tvm.runtime.device`
function, which returns a handle to a specific device, accessed
through a specific API. (e.g. ``tvm.runtime.device('cuda',0)`` gives
access to physical device ``0``, accessed through the CUDA API.)
.. _device_api.h: https://github.com/apache/tvm/blob/main/include/tvm/runtime/device_api.h
* Attribute queries - ``GetAttr`` allows different
device-specific parameters to be queried, such as the device name,
number of threads, etc. The parameters that can be queried are
defined in ``enum DeviceAttrKind`` in `device_api.h`_. Not all
query-able parameters are supported by all devices. If a parameter
cannot be queried (e.g. ``kMaxClockRate`` on Vulkan), or if a
parameter isn't applicable (e.g. ``kWarpSize`` on CPU), then those
queries should return ``nullptr``.
* Setting active device - ``SetDevice`` should set a
particular device as being active. If a ``PackedFunc`` generated by
the target-specific code gen requires execution on a device, it
should run on the active device.
* Memory management - Utilities for allocating and deallocating memory
on the device.
* Allocate data space - ``AllocDataSpace`` and ``FreeDataSpace``
allocate and free space on the device. These allocations can be
provided as inputs and outputs to an operator and make up the
primary data flow of the operator graph. It must be possible to
transfer data from the host to/from a data space. The return
value is an opaque ``void*``. While some implementations return a
memory address, this is not required, and the ``void*`` may be an
opaque handle that is interpretable only by the device backend
that generated it. The ``void*`` is used as an argument to other
backend-specific functions, such as ``CopyDataFromTo``.
* Allocate work space - ``AllocWorkspace`` and ``FreeWorkspace``
allocate and free space on the device. Unlike data space, these
are used for storage of intermediate values within an operator
definition, and are not required to be transferable to/from the
host device. If a ``DeviceAPI`` subclass does not implement these
methods, they will default to calling the corresponding
``DataSpace`` functions.
* Copy data - ``CopyDataFromTo`` should copy data from one location
to another. The type of copy is determined by the ``dev_from``
and ``dev_to`` parameters. Implementations should support copying
memory from CPU to device, from device to CPU, and from one buffer
to another on a single device. If the source or destination
locations are on the CPU, the corresponding ``void*`` points to a
CPU address that can be passed into ``memcpy``. If the source or
destinations locations are on the device, the corresponding
``void*`` was previously generated by either ``AllocDataSpace`` or
``AllocWorkspace``.
These copies are queued to execute on a specific
``TVMStreamHandle``. However, implementations should not assume
that CPU buffers remains valid or accessible after the call to
``CopyDataFromTo`` completes.
* Execution stream management - utilities for handling
``TVMStreamHandle``, which represents parallel streams of execution
used to execute commands.
* Create stream - ``CreateStream`` and ``FreeStream`` should
allocate/free a handle to a stream of execution. If a device
implements only a single queue of commands, then ``CreateStream``
should return ``nullptr``.
* Set active stream - ``SetStream`` should set a stream as being
active. While active, if a ``PackedFunc`` generated by the
target-specific code gen requires execution on a device, the work
should be submitted to the active stream.
* Synchronize to CPU - ``StreamSync`` should synchronize a stream of
execution to the CPU. The call to ``StreamSync`` should return
once all memory transfers and computations submitted prior to the
``StreamSync`` call have completed.
* Synchronize between streams - ``SyncStreamFromTo`` should
introduce a synchronization barrier between the source and
destination stream. That is, the destination stream may not
proceed beyond commands currently queued until the source stream
has completed all commands that are currently queued.
In order to be usable by the TVM framework, the new DeviceAPI should
then be registered with the following steps.
#. Create a function that instantiates the new DeviceAPI, and returns
a pointer to it::
FooDeviceAPI* FooDeviceAPI::Global() {
static FooDeviceAPI inst;
return &inst;
}
#. Register the function to the tvm registry::
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("device_api.foo", FooDeviceAPI::Global);
}
.. _base.h: https://github.com/apache/tvm/blob/main/include/tvm/runtime/base.h
#. Add an entry for the new DeviceAPI to the ``TVMDeviceExtType`` enum
in `base.h`_. The value should be an unused value greater
than ``DLDeviceType::kDLExtDev``, but less than
``DeviceAPIManager::kMaxDeviceAPI``.
#. Add a case in ``DeviceName`` in `device_api.h`_ to convert from the
enum value to a string representation. This string representation
should match the name given to ``GlobalDef().def``.
#. Add entries to the ``_DEVICE_TYPE_TO_NAME`` and ``_DEVICE_NAME_TO_TYPE`` dictionaries of
:py:class:`tvm.runtime.Device` for the new enum value.
.. _tvm-target-specific-target:
Target Definition
-----------------
The ``Target`` object is a lookup table of properties about a physical
device, its hardware/driver limits, and its capabilities. The
``Target`` is accessible both during optimization and code generation
stages. While the same ``Target`` class is used for all runtime
targets, each runtime target may need to add target-specific options.
.. _target_kind.cc: https://github.com/apache/tvm/blob/main/src/target/target_kind.cc
In `target_kind.cc`_, add a new declaration of
``TVM_REGISTER_TARGET_KIND``, passing a string name of the new target,
and the ``TVMDeviceExtType`` or ``DLDeviceType`` enum value for the
device on which that target should run. Typically, the target name
and the device name will match (e.g., the ``"cuda"`` target runs on
the ``kDLCUDA`` device). There are exceptions, such as when multiple
different code generation targets can run on the same physical device
(e.g., the ``"llvm"`` and ``"c"`` targets both run on the ``kDLCPU``
device type).
All options for a specific target kind are added with the
``add_attr_option`` function, with optional default values. A `Target`
parser can be added with ``set_target_parser`` to process
any parameters that are dynamically based on other parameters or
queried from device properties.
This argument definition defines a parser that can unpack a string
description of a target. This is done in the ``Target::Target(const
String&)`` constructor in C++, which accepts a JSON-formatted string
and is typically called using the :py:class:`tvm.target.Target` python
object. For example, ``tvm.target.Target('{"kind": "cuda",
"max_num_threads": 1024}')`` will create a ``cuda`` target, while
overriding the default maximum number of threads.
In a code generator, the target properties can be accessed using
``target->GetAttr<T>(param_name)`` in C++, or with the
``target.attrs`` dictionary in Python.
.. _tvm-target-specific-codegen:
Target Code Generators
----------------------
The code generators take an optimized ``IRModule`` and converts it
into an executable representation. Each code generator must be
registered in order to be used by the TVM framework. This is done by
registering a function named ``"target.build.foo"``, where ``foo`` is
the same name as was used in the ``TVM_REGISTER_TARGET_KIND``
definition above. ::
tvm::runtime::Module GeneratorFooCode(IRModule mod, Target target);
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("target.build.foo", GeneratorFooCode);
}
The code generator takes two arguments. The first is the ``IRModule``
to compile, and the second is the ``Target`` that describes the device
on which the code should run. Because the environment performing the
compilation is not necessarily the same as the environment that will
be executing the code, code generators should not perform any
attribute lookups on the device itself, and should instead access
parameters stored in the ``Target``.
Each function in the input ``IRModule`` should be accessible by name
in the output ``runtime::Module``.
+361
View File
@@ -0,0 +1,361 @@
.. 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.
.. _external-library-dispatch:
External Library Dispatch (BYOC)
================================
When deploying models, certain operator patterns (e.g., matmul + bias + relu) can be executed
more efficiently by vendor-optimized libraries such as cuBLAS, CUTLASS, cuDNN, or DNNL. TVM's
**BYOC (Bring Your Own Codegen)** mechanism identifies these patterns in a Relax module and
offloads them to external backends, while keeping the rest of the computation on TVM's own
generated kernels.
This document explains the BYOC pipeline: how patterns are registered, how subgraphs are
matched and extracted, how backend code generators are invoked, and how the externally compiled
code is executed at runtime.
Overview
--------
The BYOC pipeline consists of four stages:
.. code-block:: text
IRModule (high-level Relax IR)
▼ FuseOpsByPattern ← match high-level ops, create composite functions
IRModule (with Composite + Codegen attributes)
▼ RunCodegen ← invoke backend codegen via FFI
IRModule (with call_dps_packed to ExternFunc)
+ external runtime Modules
▼ LegalizeOps + FuseOps + ... ← compile remaining ops normally
▼ VM compilation ← link external modules into executable
Deployable artifact
Each stage is a Relax transformation pass that operates on the ``IRModule``:
1. **FuseOpsByPattern** — matches operator subgraphs against registered patterns and groups them
into composite functions annotated with ``Composite`` and ``Codegen`` attributes.
2. **MergeCompositeFunctions** (optional) — merges multiple composite functions targeting the same
backend when inter-operator dependencies allow.
3. **RunCodegen** — finds all functions with a ``Codegen`` attribute, invokes the corresponding
backend code generator via FFI, and replaces the original calls with ``call_dps_packed``
to externally compiled functions.
4. **Linking** — the resulting external ``runtime.Module``\ s are attached to the ``IRModule``
as the ``external_mods`` attribute and bundled into the final executable during
``relax.build()``.
Pattern Registration
--------------------
Each backend registers the operator patterns it supports in a **global pattern registry**
(``python/tvm/relax/backend/pattern_registry.py``). The registry is a static table that maps
pattern names to ``FusionPattern`` objects.
Registering patterns
~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
from tvm.relax.backend.pattern_registry import register_patterns
from tvm.relax.backend.patterns import make_matmul_pattern
register_patterns([
(
"cublas.matmul", # pattern name (prefix = backend)
*make_matmul_pattern( # returns (DFPattern, annotation_patterns)
with_bias=False,
),
_check_matmul, # check function
),
(
"cublas.matmul_bias_relu",
*make_matmul_pattern(
with_bias=True,
activation="relax.nn.relu",
),
_check_matmul,
),
# ... more patterns
])
Each entry is a tuple of ``(name, pattern, annotation_patterns, check_func)`` that gets
converted to a ``FusionPattern`` object. The name prefix (e.g., ``"cublas"``) identifies the
backend; ``get_patterns_with_prefix("cublas")`` retrieves all patterns for that backend.
Patterns registered later have **higher priority** — when a subgraph matches multiple patterns,
the highest-priority match wins.
Pattern templates
~~~~~~~~~~~~~~~~~
``python/tvm/relax/backend/patterns.py`` provides reusable templates for common patterns:
- ``make_matmul_pattern(with_bias, activation, transposed_rhs)`` — matmul with optional bias
and activation fusion
- ``make_conv2d_pattern(with_bias, activation)`` — 2D convolution
- ``make_attention_pattern()`` — multi-head attention
- ``make_residual_block_pattern()`` — residual connections
- ``make_layer_norm_pattern()`` / ``make_rms_norm_pattern()`` — normalization layers
Each template returns ``(DFPattern, Mapping[str, DFPattern])`` — the main pattern and its
annotation sub-patterns.
Check functions
~~~~~~~~~~~~~~~
The check function validates whether a matched subgraph can actually be handled by the backend.
It receives a ``PatternCheckContext`` and returns ``True`` to accept or ``False`` to reject.
Typical checks include:
- **Data type support**: verify the operand dtypes are supported (e.g., cuBLAS supports
float16, float32, int8, bfloat16, float8 for matmul).
- **Shape constraints**: verify reduction axes are constant, batch dimensions are compatible.
- **Leaking intermediates**: reject if an intermediate result is used outside the fused group
(via ``has_leaking_intermediate_variables()``).
Partitioning
------------
After patterns are registered, a backend provides a **partition function** that applies
``FuseOpsByPattern`` to an ``IRModule``:
.. code-block:: python
# python/tvm/relax/backend/cuda/cublas.py
def partition_for_cublas(mod, bind_constants=False):
patterns = get_patterns_with_prefix("cublas")
return transform.FuseOpsByPattern(
patterns, bind_constants=bind_constants, annotate_codegen=True
)(mod)
With ``annotate_codegen=True``, each matched subgraph is wrapped in a two-level function
structure:
.. code-block:: text
# Outer function — tagged for the codegen backend
@R.function
def fused_relax_matmul_cublas0(args...):
R.func_attr({"Codegen": "cublas", "global_symbol": "fused_relax_matmul_cublas0"})
...
# Inner function — identifies the specific pattern
@R.function(private=True)
def composite(args...):
R.func_attr({"Composite": "cublas.matmul_bias_relu"})
lv0 = R.matmul(x, w)
lv1 = R.add(lv0, bias)
lv2 = R.nn.relu(lv1)
return lv2
...
The outer function carries the ``Codegen`` attribute that ``RunCodegen`` uses to dispatch to the
right backend. The inner function carries the ``Composite`` attribute that the backend codegen
uses to identify which operation to emit.
MergeCompositeFunctions
~~~~~~~~~~~~~~~~~~~~~~~
When ``annotate_codegen=False``, ``FuseOpsByPattern`` only creates inner functions with
``Composite`` attributes. A separate ``MergeCompositeFunctions`` pass then groups multiple
composite functions targeting the same backend into a single outer function with ``Codegen``
and ``global_symbol`` attributes.
This is useful when multiple sequential operations should be sent to the same backend as a
single unit (e.g., a sequence of cuBLAS matmuls that share intermediate results). The pass
checks that merging does not create cyclic dependencies between groups.
Code Generation
---------------
``RunCodegen`` (``src/relax/transform/run_codegen.cc``) is the pass that triggers backend
code generation:
1. Scan the module for all functions with a ``Codegen`` attribute.
2. Group them by backend target name.
3. For each backend, look up the registered codegen function via FFI key
``"relax.ext.<backend>"`` (e.g., ``"relax.ext.cublas"``).
4. Call the codegen function, which returns an array of compiled ``runtime.Module``\ s.
5. Replace the original function calls with ``call_dps_packed(ExternFunc(...), args)``.
6. Attach the compiled modules to the ``IRModule`` as the ``external_mods`` attribute.
Codegen registration
~~~~~~~~~~~~~~~~~~~~
Each backend registers a codegen function via TVM's FFI mechanism:
.. code-block:: cpp
// src/relax/backend/contrib/cublas/codegen.cc
ffi::Array<ffi::Module> CublasCompiler(
ffi::Array<Function> functions,
ffi::Map<ffi::String, ffi::Any> options,
ffi::Map<Constant, ffi::String> constant_names) {
ffi::Array<ffi::Module> compiled_functions;
for (const auto& func : functions) {
CublasJSONSerializer serializer(constant_names, AnalyzeVar2Value(func));
serializer.serialize(func);
auto graph_json = serializer.GetJSON();
auto names = serializer.GetConstantNames();
const auto pf = ffi::Function::GetGlobalRequired("runtime.CublasJSONRuntimeCreate");
compiled_functions.push_back(
pf(GetExtSymbol(func), graph_json, names).cast<ffi::Module>());
}
return compiled_functions;
}
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("relax.ext.cublas", CublasCompiler);
}
The codegen function receives:
- ``functions``: the Relax functions with ``Codegen`` attribute to compile.
- ``options``: backend-specific compilation options.
- ``constant_names``: mapping from constant values to their names (for weight handling).
It returns an array of ``runtime.Module`` objects — one per function — that contain the
externally compiled code.
Codegen strategies
~~~~~~~~~~~~~~~~~~
TVM provides two base classes for implementing backend codegens:
- **JSONSerializer** (``src/relax/backend/contrib/codegen_json/codegen_json.h``): serializes the
composite function into a JSON graph representation. At runtime, a backend-specific JSON
runtime module interprets the graph and dispatches to library calls. Used by cuBLAS, cuDNN,
and most backends.
- **CSourceCodegen** (``src/relax/backend/contrib/codegen_c/codegen_c.h``): generates C/CUDA
source code that is compiled and linked. Used when the backend requires ahead-of-time
compilation.
Runtime Execution
-----------------
After ``RunCodegen``, the original high-level function calls are replaced with:
.. code-block:: python
R.call_dps_packed(ExternFunc("fused_relax_matmul_cublas0"), (x, w, bias), ...)
At runtime, ``call_dps_packed`` invokes the externally compiled function through the
``PackedFunc`` interface. The external ``runtime.Module``\ s (produced by the codegen) are
imported into the final executable during ``relax.build()`` and are available via the module's
function lookup mechanism.
For JSON-based backends (cuBLAS, cuDNN), the runtime module deserializes the JSON graph and
dispatches each node to the corresponding library API call. For source-based backends, the
compiled native code is called directly.
Adding a New Backend
--------------------
To add support for a new external library:
1. **Define patterns** in ``python/tvm/relax/backend/<target>/``:
- Create DFPatterns using templates from ``patterns.py`` or custom patterns.
- Write check functions to validate dtypes, shapes, and other constraints.
- Register patterns with ``register_patterns()``.
- Provide a ``partition_for_<backend>(mod)`` convenience function.
2. **Implement codegen** in ``src/relax/backend/contrib/<target>/``:
- Subclass ``JSONSerializer`` or ``CSourceCodegen``.
- Implement the visitor that converts composite functions to the target format.
- Register the codegen function as ``"relax.ext.<target>"``.
3. **Implement runtime** (for JSON-based backends):
- Create a JSON runtime module that interprets the serialized graph and dispatches
to the library's API calls.
- Register the runtime constructor as ``"runtime.<Target>JSONRuntimeCreate"``.
Supported Backends
------------------
.. list-table::
:header-rows: 1
:widths: 15 25 60
* - Backend
- Patterns
- Operations
* - cuBLAS
- ``cublas.*``
- Matmul (with bias, activation, transpose, dequantize variants)
* - CUTLASS
- ``cutlass.*``
- Matmul, conv2d, attention, residual blocks, decode matmul
* - cuDNN
- ``cudnn.*``
- Conv2d (NHWC/NCHW), stacked attention
* - DNNL
- ``dnnl.*``
- Matmul, conv2d (x86 CPU). Codegen exists at C++ level; patterns are
defined in tests rather than pre-registered.
Source Code Map
---------------
.. list-table::
:header-rows: 1
:widths: 50 50
* - Path
- Contents
* - ``python/tvm/relax/backend/pattern_registry.py``
- Pattern registry API (register_patterns, get_patterns_with_prefix)
* - ``python/tvm/relax/backend/patterns.py``
- Reusable pattern templates (make_matmul_pattern, etc.)
* - ``python/tvm/relax/backend/cuda/cublas.py``
- cuBLAS patterns and partition_for_cublas
* - ``python/tvm/relax/backend/cuda/cutlass.py``
- CUTLASS patterns and partition_for_cutlass
* - ``python/tvm/relax/backend/cuda/cudnn.py``
- cuDNN patterns and partition_for_cudnn
* - ``src/relax/backend/pattern_registry.cc``
- Pattern registry C++ implementation
* - ``src/relax/transform/run_codegen.cc``
- RunCodegen pass (CodeGenRunner)
* - ``src/relax/transform/merge_composite_functions.cc``
- MergeCompositeFunctions pass
* - ``src/relax/backend/contrib/cublas/codegen.cc``
- cuBLAS codegen (JSONSerializer-based)
* - ``src/relax/backend/contrib/cutlass/codegen.cc``
- CUTLASS codegen
* - ``src/relax/backend/contrib/codegen_json/codegen_json.h``
- JSONSerializer base class
* - ``src/relax/backend/contrib/codegen_c/codegen_c.h``
- CSourceCodegen base class
+388
View File
@@ -0,0 +1,388 @@
.. 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.
.. _fusion-arch:
Operator Fusion
===============
Operator fusion is one of the most impactful optimizations in TVM. Instead of launching one kernel
per operator (e.g., conv2d, bias_add, relu), fusion merges multiple operators into a single kernel,
eliminating intermediate memory allocations and kernel launch overhead.
TVM provides two complementary fusion mechanisms:
- **Automatic fusion** (``FuseOps`` + ``FuseTIR``): groups operators based on their computational
patterns using a post-dominator analysis algorithm.
- **Pattern-based fusion** (``FuseOpsByPattern``): groups operators that match user-defined
dataflow patterns, typically for offloading to external backends (cuBLAS, CUTLASS, DNNL, etc.).
Both produce the same output: Relax functions marked with ``Primitive=True`` that are later
lowered to fused TIR kernels or dispatched to external libraries.
Overview
--------
Fusion involves three passes:
.. code-block:: text
IRModule (after LegalizeOps)
▼ AnnotateTIROpPattern ← label each op (elementwise, reduce, etc.)
IRModule (annotated)
▼ FuseOps ← group ops into fused Relax functions
IRModule (with fused functions marked Primitive=True)
▼ FuseTIR ← merge TIR PrimFuncs inside each group
IRModule (fused TIR kernels)
In the compilation pipeline, these passes appear in the backend-specific ``legalize_passes``
phase. For example, the CUDA pipeline (``python/tvm/relax/backend/cuda/pipeline.py``) runs:
.. code-block:: python
LegalizeOps() # lower Relax ops to call_tir
AnnotateTIROpPattern() # annotate pattern kinds
FoldConstant()
FuseOps() # group ops
FuseTIR() # merge TIR functions
Operator Pattern Classification
-------------------------------
Before fusion, ``AnnotateTIROpPattern`` analyzes each TIR function in the module and assigns
an ``OpPatternKind``. The fusion algorithm uses these pattern kinds to decide which operators
can be fused together.
.. list-table::
:header-rows: 1
:widths: 20 10 70
* - Pattern Kind
- Value
- Description
* - ``kElemWise``
- 0
- Elementwise: one-to-one input/output mapping (e.g., ``add``, ``relu``, ``exp``).
* - ``kBroadcast``
- 1
- Broadcasting: output axes map to input axes in order, but some input axes may be
broadcast (e.g., ``bias_add``). Note: ``transpose`` is **not** broadcast because axes
are reordered.
* - ``kInjective``
- 2
- Injective: each output element depends on a single input element, but the mapping may
be non-trivial (e.g., ``reshape``, ``concatenate``, ``transpose``).
* - ``kCommReduce``
- 3
- Communicative reduction: output elements aggregate over input elements
(e.g., ``sum``, ``max``, ``mean``).
* - ``kOutEWiseFusable``
- 4
- Complex operation whose output can accept elementwise followers, but cannot chain
with another complex op (e.g., ``conv2d``, ``matmul``, ``dense``).
* - ``kTuple``
- 7
- Tuple node. Can fuse into subsequent injective ops but is treated specially.
* - ``kOpaque``
- 8
- Opaque: cannot be fused (e.g., external function calls, operations with side effects).
These kinds form an ordering: lower values are "simpler" and more fusable. The fusion algorithm
uses ``CombinePattern(lhs, rhs) = max(lhs, rhs)`` when merging patterns along a path.
FuseOps: Automatic Fusion
-------------------------
``FuseOps`` (``src/relax/transform/fuse_ops.cc``) groups bindings in a dataflow block into
new Relax functions. It operates only within ``DataflowBlock``\ s — if your module doesn't have
any, run ``ConvertToDataflow`` first.
Algorithm
~~~~~~~~~
The fusion algorithm addresses diamond-shaped dataflow branches, where a single producer
(e.g., conv2d) has multiple consumers that eventually reconverge:
.. code-block:: text
conv2d
/ | \
/ | \
op op op
\ | /
\ | /
elemwise add
At the point of ``conv2d``, we don't know if all future paths will merge. The algorithm uses
**post-dominator analysis** to resolve this:
1. **Build forward graph**: construct an ``IndexedForwardGraph`` from the dataflow block.
Each node has an ``OpPatternKind`` and a list of forward edges.
2. **Build post-dominator tree**: compute the immediate post-dominator of each node using
Least Common Ancestor (LCA) on the DAG. The post-dominator of a node is the closest
downstream node where **all** future paths converge.
3. **Fuse groups**: for each node in topological order, check if it can be fused with its
immediate post-dominator:
- **CheckPath**: verify that all paths from the node to its post-dominator satisfy the
fusion conditions (pattern compatibility, depth limits, argument limits).
- **CommitFuse**: mark all intermediate nodes as belonging to the same group using a
Union-Find data structure.
4. **Create grouped functions**: extract each group into a new ``relax.Function`` with the
attribute ``Primitive=True``. Replace the original bindings with a call to the grouped
function.
Fusion rules
~~~~~~~~~~~~
The key fusion decisions depend on the ``OpPatternKind`` of the source, the path, and the
post-dominator. The algorithm runs in three phases (via ``GraphPartitioner::RunFuse``) so that
higher-complexity ops get a chance to fuse first:
- **Phase 0**: ``kOutEWiseFusable`` ops (e.g., ``conv2d``) can fuse with their elementwise
post-dominator if all intermediate ops are broadcast or simpler. This enables patterns like
conv2d + bias_add + relu. Two ``kOutEWiseFusable`` ops cannot fuse together.
- **Phase 1**: ``kInjective`` and ``kTuple`` ops can fuse only when all paths to the
post-dominator are injective or simpler. This is deferred to phase 1 so that
``kOutEWiseFusable`` groups are finalized first.
- **Phase 2**: fuse injective ops into intermediate tuple nodes that have already been absorbed
by subsequent injective groups.
``kElemWise`` / ``kBroadcast`` ops are processed in **every** phase (not restricted to one):
they can fuse into a post-dominator that is injective or reduction. The sink (final node) may
also be a ``kOutEWiseFusable`` group that was formed in phase 0 — this is how elementwise
producers merge into an existing conv2d fusion group.
Additional constraints:
- **Reduction** (``kCommReduce``) ops never initiate fusion — they act as sinks only. Elementwise
and broadcast producers can fuse *into* a reduction, but a reduction cannot fuse forward.
- **Opaque** ops are fusion barriers.
- A group cannot exceed ``kMaxFusedOps`` (256) nodes or the maximum function argument count.
Example
~~~~~~~
Given two elementwise ops (``add``, ``exp``) and one injective op (``squeeze``).
The examples below are simplified pseudocode — real TVMScript would reference TIR functions
via ``cls.func_name``:
.. code-block:: python
# Before FuseOps (simplified)
@R.function
def main(x: R.Tensor((10, 20), "float32")):
with R.dataflow():
lv0 = R.call_tir(add, (x, const_1), out_ty=R.Tensor((10, 20), "float32"))
lv1 = R.call_tir(exp, (lv0,), out_ty=R.Tensor((10, 20), "float32"))
gv = R.call_tir(squeeze, (lv1,), out_ty=R.Tensor((10, 20), "float32"))
R.output(gv)
return gv
After ``FuseOps``, all three are grouped into a single function:
.. code-block:: python
# After FuseOps
@R.function(private=True)
def fused_add_exp_squeeze(x, p0):
R.func_attr({"Primitive": True})
with R.dataflow():
lv0 = R.call_tir(add, (x, p0), ...)
lv1 = R.call_tir(exp, (lv0,), ...)
gv = R.call_tir(squeeze, (lv1,), ...)
R.output(gv)
return gv
@R.function
def main(x: R.Tensor((10, 20), "float32")):
with R.dataflow():
gv = fused_add_exp_squeeze(x, const_1)
R.output(gv)
return gv
FuseTIR: Merging TIR Functions
------------------------------
``FuseTIR`` (``src/relax/transform/fuse_tir.cc``) takes the grouped Relax functions produced by
``FuseOps`` and merges their internal TIR ``PrimFunc``\ s into a single TIR function.
Before ``FuseTIR``, a fused group still contains multiple ``R.call_tir`` calls to separate
TIR functions. ``FuseTIR`` inlines and merges them:
.. code-block:: text
Before FuseTIR:
fused_add_exp_squeeze:
call_tir(add, ...) → separate TIR PrimFunc
call_tir(exp, ...) → separate TIR PrimFunc
call_tir(squeeze, ...) → separate TIR PrimFunc
After FuseTIR:
fused_add_exp_squeeze: → single merged TIR PrimFunc
The merged function eliminates intermediate buffers — the output of ``add`` is directly consumed
by ``exp`` without writing to and reading from global memory. This is the core performance benefit
of fusion.
Internally, ``FuseTIR`` uses a ``SymbolicMatcher`` to align symbolic shape variables across the
TIR functions being merged, ensuring that dimensions are correctly mapped when combining buffer
accesses.
FuseOpsByPattern: Pattern-Based Fusion
--------------------------------------
While ``FuseOps`` makes fusion decisions automatically based on operator patterns,
``FuseOpsByPattern`` lets you specify exactly which operator combinations to fuse using
the Relax :ref:`Dataflow Pattern Language (DPL) <relax-dpl>`.
This is primarily used for **backend-specific dispatch**: identifying operator subgraphs that
should be offloaded to external libraries like cuBLAS, CUTLASS, cuDNN, or DNNL.
FusionPattern
~~~~~~~~~~~~~
A ``FusionPattern`` (``python/tvm/relax/transform/transform.py``) defines what to match:
.. code-block:: python
from tvm.relax.dpl import wildcard, is_op
from tvm.relax.transform import FusionPattern
# Match: matmul(x, w) + bias
x = wildcard()
w = wildcard()
bias = wildcard()
matmul = is_op("relax.matmul")(x, w)
out = is_op("relax.add")(matmul, bias)
pattern = FusionPattern(
name="cutlass.matmul_bias",
pattern=out,
annotation_patterns={"matmul": matmul, "bias": bias},
check=my_check_function, # optional validation
)
Fields:
- ``name``: pattern identifier, typically prefixed with the backend name (e.g.,
``"cutlass.matmul_bias"``).
- ``pattern``: a DFPattern describing the subgraph to match. See the
:ref:`DPL deep dive <relax-dpl>` for the full pattern language.
- ``annotation_patterns``: a mapping of names to sub-patterns within the main pattern. These
are extracted during matching and made available to the ``check`` function and
``attrs_getter``.
- ``check``: an optional ``Callable[[PatternCheckContext], bool]`` that validates whether
a match should be accepted. Receives the matched expression, annotated sub-expressions,
variable usages, and binding information.
- ``attrs_getter``: an optional function that extracts attributes (e.g., transpose flags,
data types) from the matched expressions to annotate the grouped function.
Applying patterns
~~~~~~~~~~~~~~~~~
.. code-block:: python
from tvm.relax.transform import FuseOpsByPattern
mod = FuseOpsByPattern(
patterns=[pattern1, pattern2, ...], # ordered by priority
bind_constants=True,
annotate_codegen=False,
)(mod)
Key parameters:
- ``patterns``: a list of ``FusionPattern`` objects, ordered by priority. Higher-priority
patterns come first — if a subgraph matches multiple patterns, the first match wins.
- ``bind_constants``: if ``True``, constants used by the matched subgraph are captured inside
the grouped function.
- ``annotate_codegen``: if ``True``, wraps each composite function with an outer function
annotated with ``"Codegen"`` and ``"global_symbol"`` attributes for external backend dispatch.
The ``"Codegen"`` value is derived from the pattern name prefix (e.g., ``"dnnl"`` from
``"dnnl.conv2d_relu"``).
PatternCheckContext
~~~~~~~~~~~~~~~~~~~
The ``check`` function receives a ``PatternCheckContext`` with:
- ``matched_expr``: the root expression matched by the pattern.
- ``annotated_expr``: a mapping from annotation pattern names to their matched expressions.
- ``matched_bindings``: variable-to-value bindings within the matched subgraph.
- ``var_usages``: a mapping from variable definitions to all their uses in the function.
- ``value_to_bound_var``: reverse mapping from values to the variables they are bound to.
This context enables sophisticated validation logic, such as checking that an intermediate
result is not used outside the fused group, or verifying data type compatibility.
How Backends Use Fusion
-----------------------
The default backend pipelines (CUDA, ROCm, CPU, etc.) all include ``FuseOps`` + ``FuseTIR``
in their ``legalize_passes`` phase for automatic fusion, as shown in the `Overview`_ above.
For external library dispatch (cuBLAS, CUTLASS, cuDNN, DNNL), ``FuseOpsByPattern`` is used
separately. These are **not** included in the default pipeline — users add them explicitly
when building a custom compilation flow. The typical sequence is:
1. **Pattern-based dispatch** (``FuseOpsByPattern``): identify subgraphs that should be
offloaded to external libraries. For example, CUTLASS patterns match
matmul+bias+activation combinations (``python/tvm/relax/backend/cuda/cutlass.py``).
Functions marked by patterns are annotated with ``Composite`` and optionally ``Codegen``
attributes. See :ref:`external-library-dispatch` for the full BYOC pipeline.
2. **Automatic fusion** (``FuseOps`` + ``FuseTIR``): remaining operators that were not
matched by backend patterns are fused automatically based on their pattern kinds.
Source Code Map
---------------
.. list-table::
:header-rows: 1
:widths: 50 50
* - Path
- Contents
* - ``src/relax/transform/fuse_ops.cc``
- FuseOps and FuseOpsByPattern implementation
* - ``src/relax/analysis/graph_partitioner.h``
- IndexedForwardGraph, DominatorTree, GraphPartitioner (Union-Find)
* - ``src/relax/transform/fuse_tir.cc``
- FuseTIR implementation, SymbolicMatcher
* - ``include/tvm/relax/op_attr_types.h``
- ``OpPatternKind`` enum definition
* - ``python/tvm/relax/transform/transform.py``
- Python API: FuseOps, FuseTIR, FuseOpsByPattern, FusionPattern
* - ``python/tvm/relax/dpl/``
- Dataflow Pattern Language (DFPattern, is_op, wildcard, etc.)
* - ``python/tvm/relax/backend/cuda/cutlass.py``
- Example: CUTLASS fusion patterns
* - ``python/tvm/relax/backend/cuda/cublas.py``
- Example: cuBLAS fusion patterns
+436
View File
@@ -0,0 +1,436 @@
.. 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.
Design and Architecture
=======================
This document is intended for developers who want to understand the architecture of Apache TVM and/or actively develop on the project.
This page is organized as follows:
- The `Overall Flow`_ gives an overview of the steps that TVM takes to turn a high level description of a model into a deployable module.
To get started, please read this section first.
- Brief introduction to the key components of the TVM stack. Feel free to also check out the :ref:`TensorIR Deep Dive <tensor-ir-deep-dive>`
and :ref:`Relax Deep Dive <relax-deep-dive>` for more details about the two major components in the TVM stack.
This guide provides a few complementary views of the architecture.
First, we review a single end-to-end compilation flow and discuss the key data structures and the transformations.
This runtime-based view focuses on the interactions of each components when running the compiler.
Then we will review the logical modules of the codebase and their relationship. This part provides a static overarching view of the design.
Overall Flow
------------
In this guide, we will study an example compilation flow in the compiler. The figure below shows the flow. At a high-level, it contains several steps:
- **Model Creation**: Create the IRModule to be optimized and compiled, which contains a collection of functions that internally represent the model.
Users can manually construct IRModule via NNModule, TVMScript, or import a pre-trained model from Relax frontend.
- **Transformation**: The compiler transforms an IRModule to another functionally equivalent or approximately
equivalent(e.g. in the case of quantization) IRModule. Many of the transformations are target (backend) independent.
We also allow target to affect the configuration of the transformation pipeline.
- **Target Translation**: The compiler translates(codegen) the IRModule to an executable format specified by the target.
The target translation result is encapsulated as a `runtime.Module` that can be exported, loaded, and executed on the target runtime environment.
- **Runtime Execution**: the user loads back a `runtime.Module` and runs the compiled functions in the supported runtime environment.
.. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/design/tvm_overall_flow.svg
:align: center
:width: 80%
Key data structures
~~~~~~~~~~~~~~~~~~~
One of the best ways to design and understand a complex system is to identify the key data structures and APIs that
manipulate (transform) these data structures. Once we identified the key data structures, we can then breakdown a system into logical
components that either define a collection of key data structures or transformations among the data structures.
**IRModule** is the primary data structure used across the entire stack. An IRModule (intermediate representation module)
contains a collection of functions. Currently, we support two primary variants of functions.
- **relax::Function** is a high-level functional program representation. A relax.Function represents high-level graph structure,
usually corresponds to an end-to-end model or a sub-graph of the overall model. You can view a relax.Function as a computational
graph with additional support for control-flow, and complex data structures.
- **tirx::PrimFunc** is a low-level program representation that contains elements including loop-nest choices, multi-dimensional load/store,
threading, and vector/tensor instructions. It is usually used to represent an operator program that executes a (possibly-fused) layer in a model.
During the compilation and transformation, all relax operators are lowered to ``tirx::PrimFunc`` or ``TVM PackedFunc``, which can be executed directly
on the target device, while the calls to relax operators are lowered to calls to low-level functions (e.g. ``R.call_tir`` or ``R.call_dps_packed``).
Transformations
~~~~~~~~~~~~~~~
Now that we have covered the key data structures, let us talk about the transformations. Each transformation could serve one of the following purposes:
- optimization: transform a program to an equivalent, possibly more optimized version.
- lowering: transform a program to a lower-level representation that is closer to the target.
relax transformations
^^^^^^^^^^^^^^^^^^^^^
relax transformations contain a collection of passes that apply to relax functions. The optimizations include common graph-level
optimizations such as constant folding and dead-code elimination for operators, and backend-specific optimizations such as library dispatch.
TensorIR transformations
^^^^^^^^^^^^^^^^^^^^^^^^
- **TensorIR schedule**: TensorIR schedules are designed to optimize the TensorIR functions for a specific target, with user-guided instructions and control how the target code is generated.
For CPU targets, a TensorIR PrimFunc can generate valid code and execute on the target device without schedule but with very-low performance. However, for GPU targets, the schedule is essential
for generating valid code with thread bindings. For more details, please refer to the :ref:`TensorIR Transformation <tirx-transform>` section. Additionally, we provides ``MetaSchedule`` to
automate the search of TensorIR schedule.
- **Lowering Passes**: These passes usually perform after the schedule is applied, transforming a TensorIR PrimFunc into another functionally equivalent PrimFunc, but closer to the
target-specific representation. For example, there are passes to flatten multi-dimensional access to one-dimensional pointer access, to expand the intrinsics into target-specific ones,
and to decorate the function entry to meet the runtime calling convention.
Many low-level optimizations can be handled in the target phase by the LLVM,
CUDA C, and other target compilers. As a result, we leave low-level
optimizations such as register allocation to the downstream compilers and only
focus on optimizations that are not covered by them.
cross-level transformations
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Apache TVM enables cross-level optimization of end-to-end models. As the IRModule includes both Relax and TensorIR functions, the cross-level transformations are designed to mutate
the IRModule by applying different transformations to these two types of functions.
For example, ``relax.LegalizeOps`` pass mutates the IRModule by lowering relax operators, adding corresponding TensorIR PrimFunc into the IRModule, and replacing the relax operators
with calls to the lowered TensorIR PrimFunc. Another example is the operator fusion pipeline
(``relax.FuseOps`` + ``relax.FuseTIR``), which fuses multiple consecutive tensor operations into a
single kernel. See :ref:`fusion-arch` for a detailed explanation of the fusion algorithm, operator
pattern classification, and pattern-based fusion for external backends.
.. toctree::
:maxdepth: 1
fusion
Target Translation
~~~~~~~~~~~~~~~~~~
The target translation phase transforms an IRModule to the corresponding target executable format.
For backends such as x86 and ARM, we use the LLVM IRBuilder to build in-memory LLVM IR.
We can also generate source-level languages such as CUDA C and OpenCL.
Finally, we support direct translations of a Relax function (sub-graph) to specific targets via external code generators.
See :ref:`codegen-arch` for how TIR functions are compiled to native code through the LLVM and
Source codegen families.
See :ref:`external-library-dispatch` for the full BYOC (Bring Your Own Codegen) pipeline that
offloads operator subgraphs to vendor libraries like cuBLAS, CUTLASS, and cuDNN.
It is important that the final code generation phase is as lightweight as possible. Vast majority of transformations
and lowering should be performed before the target translation phase.
.. toctree::
:maxdepth: 1
codegen
external_library_dispatch
We also provide a Target structure to specify the compilation target.
The transformations before the target translation phase can also be affected by the target — for example,
a target's vector length would change the vectorization behavior.
Runtime Execution
~~~~~~~~~~~~~~~~~
The main goal of TVM's runtime is to provide a minimal API for loading and executing the compiled artifact in a language of their choice, including Python, C++, Rust, Go, Java, and JavaScript. The code snippet below shows such an example in Python:
.. code-block:: python
import tvm
# Example runtime execution program in python, with type annotated
mod: tvm.runtime.Module = tvm.runtime.load_module("compiled_artifact.so")
arr: tvm.runtime.Tensor = tvm.runtime.tensor([1, 2, 3], device=tvm.cuda(0))
fun: tvm_ffi.Function = mod["addone"]
fun(arr)
print(arr.numpy())
:py:class:`tvm.runtime.Module` encapsulates the result of compilation. A runtime.Module contains a GetFunction method to obtain :py:class:`tvm_ffi.Function` instances by name.
:py:class:`tvm_ffi.Function` is a type-erased function interface for both the generated functions. A tvm_ffi.Function can take arguments and return values with the
following types: POD types(int, float), string, tvm_ffi.Function, runtime.Module, runtime.Tensor, and other sub-classes of runtime.Object.
:py:class:`tvm.runtime.Module` and :py:class:`tvm_ffi.Function` are powerful mechanisms to modularize the runtime. For example, to get the above `addone` function on CUDA, we can use LLVM to generate the host-side code to compute the launching parameters(e.g. size of the thread groups) and then call into another tvm_ffi.Function from a CUDAModule that is backed by the CUDA driver API. The same mechanism can be used for OpenCL kernels.
The above example only deals with a simple `addone` function. The code snippet below gives an example of an end-to-end model execution using the Relax Virtual Machine, which is built on the same runtime.Module and tvm_ffi.Function interface:
.. code-block:: python
import tvm
from tvm import relax
# Load the compiled artifact
mod: tvm.runtime.Module = tvm.runtime.load_module("resnet18.so")
# Create a VM instance on cuda(0)
vm = relax.VirtualMachine(mod, tvm.cuda(0))
data: tvm.runtime.Tensor = get_input_data()
# Run the model — vm["main"] returns a PackedFunc
result = vm["main"](data).numpy()
The main take away is that runtime.Module and runtime.PackedFunc are sufficient to encapsulate both operator level programs (such as addone), as well as the end-to-end models.
Summary and Discussions
~~~~~~~~~~~~~~~~~~~~~~~
In summary, the key data structures in the compilation flows are:
- IRModule: contains relax.Function and tirx.PrimFunc
- runtime.Module: contains runtime.PackedFunc
Most parts of the compilation are transformations among the key data structures.
- relax/transform and tirx/transform are deterministic rule-based transformations
- meta-schedule contains the search-based transformations
Finally, the compilation flow example is only a typical use-case of the TVM stack.
We expose these key data structures and transformations to python and C++ APIs. As a result, you can use TVM just like the way you use numpy,
except that the data structure of interest changes from the numpy.ndarray to tvm.IRModule. Here are some example use-cases:
- Directly construct IRModule using the python API.
- Compose a custom set of transformations(e.g. customize quantization).
- Manipulate the IR directly using TVM's python API.
tvm/support
-----------
The support module contains the most common utilities for the infrastructure, such as generic arena allocator, socket, and logging.
tvm/runtime
-----------
The runtime serves as the foundation of the TVM stack. It provides the mechanism to load and execute compiled artifacts.
The runtime defines a stable standard set of C APIs to interface with frontend languages such as Python and Rust.
`runtime::Object` is one of the primary data structures in TVM runtime besides the `ffi::Function`.
It is a reference-counted base class with a type index to support runtime type checking and downcasting.
The object system allows the developer to introduce new data structures to the runtime, such as Array, Map, and new IR data structures.
Besides deployment use-cases, the compiler itself also makes heavy use of TVM's runtime mechanism.
All of the IR data structures are subclasses of `runtime::Object`, as a result, they can be directly accessed and manipulated from the Python frontend.
We use the PackedFunc mechanism to expose various APIs to the frontend.
Runtime support for different hardware backends are defined in subdirectories of runtime(e.g. runtime/opencl).
These hardware-specific runtime modules define APIs for device memory allocation and device function serialization.
`runtime/rpc` implements an RPC support for PackedFunc. We can use the RPC mechanism to send a cross-compiled library to a remote
device and benchmark the execution performance. The rpc infrastructure enables data collection from a wide range of hardware backends
for learning-based optimizations.
.. toctree::
:maxdepth: 1
runtime
.. toctree::
:maxdepth: 1
introduction_to_module_serialization
Relax Virtual Machine
~~~~~~~~~~~~~~~~~~~~~
Relax defines *what* to compute — it is a graph-level IR that describes the operators and dataflow
of a model. The Relax Virtual Machine (VM) handles *how* to run it — it is the runtime component
that executes the compiled result. The VM uses a register-based interpreter with only four opcodes
(``Call``, ``Ret``, ``Goto``, ``If``) and performs no mathematical computation itself — it
orchestrates control flow while dispatching actual work to compiled TIR kernels or external
libraries.
See :ref:`relax-vm-arch` for the full architecture documentation, including the compilation
pipeline, instruction set details, execution model, and Python interface.
.. toctree::
:maxdepth: 1
relax_vm
Disco: Distributed Runtime
~~~~~~~~~~~~~~~~~~~~~~~~~~
Disco is TVM's distributed runtime for executing models across multiple devices. When a model is
too large to fit on a single GPU, the ``relax.distributed`` module annotates how tensors should be
partitioned and placed across a mesh of devices at compile time. Disco then takes over at runtime:
it manages a group of workers, dispatches the compiled program to all of them simultaneously, and
coordinates inter-device communication through collective operations such as allreduce, allgather,
broadcast, and scatter.
The central abstraction is the ``Session``, which owns the workers and exposes a SPMD-style
programming interface. Every object that lives on workers is represented by a ``DRef`` — a
distributed reference that maps to a concrete value on each worker. When the controller invokes a
``DPackedFunc`` through the session, all workers execute the same PackedFunc call synchronously, each
operating on its own local shard. Compiled VM modules can be loaded into a session as ``DModule``
objects and called in the same fashion. The session also provides collective primitives backed by
NCCL or RCCL, so that workers can exchange partial results without routing data through the
controller.
Three session backends cover different deployment topologies. ``ThreadedSession`` spawns workers as
threads within a single process — this is the most common choice for multi-GPU inference on a
single machine. ``ProcessSession`` launches workers as separate OS processes connected by pipes,
providing stronger isolation. ``SocketSession`` extends the model to multi-node clusters by
connecting workers across machines via TCP sockets.
tvm/node
--------
The node module adds additional features on top of the `runtime::Object` for IR data structures.
The main features include reflection, serialization, structural equivalence, and hashing.
Thanks to the node module, we can directly access any field of the TVM's IRNode by their name in Python.
.. code-block:: python
x = tvm.tirx.Var("x", "int32")
y = tvm.tirx.Add(x, x)
# a and b are fields of a tirx.Add node
# we can directly use the field name to access the IR structures
assert y.a == x
We can also serialize arbitrary IR node into a JSON format, and load them back.
The ability to save/store, and inspect an IR node provides a foundation for making the compiler more accessible.
tvm/ir
------
The `tvm/ir` folder contains the unified data structure and interfaces across all IR function variants.
The components in `tvm/ir` are shared by `tvm/relax` and `tvm/tirx`, notable ones include
- IRModule
- Type
- PassContext and Pass
- Op
Different variants of functions(e.g. relax.Function and tirx.PrimFunc) can co-exist in an IRModule.
While these variants may not have the same content representation, they use the same data structure to represent types.
As a consequence, we use the same data structure to represent function (type) signatures of these variants.
The unified type system allows one function variant to call another function
once we clearly define the calling convention. This opens doors for future cross-function-variant optimizations.
We also provide a unified PassContext for configuring the pass behavior, and common composite passes to execute a pass pipeline.
The following code snippet gives an example of PassContext configuration.
.. code-block:: python
# configure the behavior of the tirx.UnrollLoop pass
with tvm.transform.PassContext(config={"tirx.UnrollLoop": { "auto_max_step": 10 }}):
# code affected by the pass context
Op is the common class to represent all system-defined primitive operator/intrinsics.
Developers can register new Ops as well as their additional attributes(e.g. whether the Op is elementwise) to the system.
.. toctree::
:maxdepth: 1
pass_infra
tvm/script (TVMScript)
----------------------
TVMScript is a Python-based DSL for writing TVM IR. It allows users to define ``IRModule``\ s
— containing both Relax functions and TIR ``PrimFunc``\ s — using familiar Python syntax with
three import aliases: ``I`` (module-level), ``T`` (TIR), and ``R`` (Relax). Although TVMScript
uses Python syntax, it is not executed by the Python interpreter — decorators like
``@I.ir_module``, ``@T.prim_func``, and ``@R.function`` extract the Python AST and transform
it into TVM IR through a parser and IR builder pipeline.
TVMScript also supports **roundtrip**: any ``IRModule`` can be printed back to TVMScript via
``mod.script()`` and re-parsed to produce a structurally equivalent module. See
:ref:`tvmscript-arch` for the full architecture documentation, including the parser dispatch
mechanism, IR builder frame stack, printer pipeline, and syntax reference.
.. toctree::
:maxdepth: 1
tvmscript
tvm/target
----------
The target module contains all the code generators that translate an IRModule to a target runtime.Module.
It also provides a common `Target` class that describes the target.
Targets can be constructed from a registered tag, a configuration dictionary, or a tag with attribute overrides:
.. code-block:: python
from tvm.target import Target
# From a registered tag
target = Target("nvidia/nvidia-a100")
# From a config dictionary
target = Target({"kind": "cuda", "arch": "sm_80"})
# From a tag with attribute overrides
target = Target({"tag": "nvidia/nvidia-a100", "l2_cache_size_bytes": 12345})
Use ``Target.list_kinds()`` to see all available target kinds, and ``target.attrs`` to inspect
target attributes.
The compilation pipeline can be customized according to the target by querying the attribute information
in the target and builtin information registered to each target id(cuda, opencl).
.. toctree::
:maxdepth: 1
device_target_interactions
tvm/relax
---------
Relax is the high-level IR used to represent the computational graph of a model. Various optimizations are defined in ``relax.transform``.
Note that Relax usually works closely with the TensorIR IRModule, most of the transformations are applied on both Relax and TensorIR functions
in the IRModule. Please refer to the :ref:`Relax Deep Dive <relax-deep-dive>` for more details.
tvm/tirx
--------
``tirx`` contains the core IR definitions and lowering infrastructure
for TensorIR (split from the former ``tir`` module). ``tirx::PrimFunc``
represents low-level tensor functions that can be transformed by tirx passes.
The tirx module includes:
- IR data structures (PrimFunc, Buffer, SBlock, expressions, statements).
- Analysis passes in ``tirx/analysis``.
- Transformation and lowering passes in ``tirx/transform``.
tvm/s_tir
---------
``s_tir`` (Schedulable TIR, split from the former ``tir`` module) contains
schedule primitives and auto-tuning tools that operate on ``tirx::PrimFunc``:
- Schedule primitives to control code generation (tiling, vectorization, thread
binding) in ``s_tir/schedule``.
- Builtin tensor intrinsics in ``s_tir/tensor_intrin``.
- MetaSchedule for automated performance tuning.
- DLight for pre-defined, high-performance schedules.
Please refer to the :ref:`TensorIR Deep Dive <tensor-ir-deep-dive>` for more details.
tvm/arith
---------
This module is closely tied to TensorIR. One of the key problems in the low-level code generation is the analysis of the indices'
arithmetic properties — the positiveness, variable bound, and the integer set that describes the iterator space. arith module provides
a collection of tools that do (primarily integer) analysis. A TensorIR pass can use these analyses to simplify and optimize the code.
tvm/te and tvm/topi
-------------------
TE stands for Tensor Expression. TE is a domain-specific language (DSL) for describing tensor computations. Importantly, a tensor expression
itself is not a self-contained function that can be stored into IRModule. We can use ``te.create_prim_func`` to convert a tensor expression to a ``tirx::PrimFunc``
and then integrate it into the IRModule.
While possible to construct operators directly via TensorIR or tensor expressions (TE) for each use case, it is tedious to do so.
`topi` (Tensor operator inventory) provides a set of pre-defined operators defined by numpy and found in common deep learning workloads.
@@ -0,0 +1,193 @@
.. 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.
Introduction to Module Serialization
====================================
When to deploy TVM runtime module, no matter whether it is CPU or GPU, TVM only needs one single dynamic
shared library. The key is our unified module serialization mechanism. This document will introduce TVM module
serialization format standard and implementation details.
*************
Serialization
*************
The entrance API is ``export_library`` of ``tvm.runtime.Module``.
Inside this function, we will do the following steps:
1. Collect all DSO modules (LLVM modules and C modules)
2. Once we have DSO modules, we will call ``save`` function to save them into files.
3. Next, we will check whether we have imported modules, such as CUDA,
OpenCL or anything else. We don't restrict the module type here.
Once we have imported modules, we will create one file named ``devc.o`` / ``dev.cc``
(so that we could embed the binary blob data of import modules into one dynamic shared library),
then call function ``_PackImportsToLLVM`` or ``_PackImportsToC`` to do module serialization.
4. Finally, we call ``fcompile`` which invokes ``_cc.create_shared`` to get
dynamic shared library.
.. note::
1. For C source modules, we will compile them and link them together with the DSO module.
2. Use ``_PackImportsToLLVM`` or ``_PackImportsToC`` depends on whether we enable LLVM in TVM.
They achieve the same goal in fact.
***************************************************
Under the Hood of Serialization and Format Standard
***************************************************
As said before, we will do the serialization work in the ``_PackImportsToLLVM`` or ``_PackImportsToC``.
They both call ``SerializeModule`` to serialize the runtime module. In ``SerializeModule``
function, we firstly construct one helper class ``ModuleSerializer``. It will take ``module`` to do some
initialization work, like marking module index. Then we could use its ``SerializeModule`` to serialize module.
For better understanding, let us dig the implementation of this class a little deeper.
The following code is used to construct ``ModuleSerializer``:
.. code:: c++
explicit ModuleSerializer(runtime::Module mod) : mod_(mod) {
Init();
}
private:
void Init() {
CreateModuleIndex();
CreateImportTree();
}
In ``CreateModuleIndex()``, We will inspect module import relationship
using DFS and create index for them. Note the root module is fixed at
location 0. In our example, we have module relationship like this:
.. code:: c++
llvm_mod:imports
- cuda_mod
So LLVM module will have index 0, CUDA module will have index 1.
After constructing module index, we will try to construct import tree (``CreateImportTree()``),
which will be used to restore module import relationship when we load
the exported library back. In our design, we use CSR format to store
import tree, each row is parent index, the child indices correspond to its children
index. In code, we use ``import_tree_row_ptr_`` and
``import_tree_child_indices_`` to represent them.
After initialization, we could serialize module using ``SerializeModule`` function.
In its function logic, we will assume the serialization format like this:
.. code:: c++
binary_blob_size
binary_blob_type_key
binary_blob_logic
binary_blob_type_key
binary_blob_logic
...
_import_tree
_import_tree_logic
``binary_blob_size`` is the number of blobs we will have in this
serialization step. There will be three blobs in our example which
are created for LLVM module, CUDA module, and ``_import_tree``, respectively.
``binary_blob_type_key`` is the blob type key of module. For LLVM / C module, whose
blob type key is ``_lib``. For CUDA module, it is ``cuda``, which could be got by ``module->type_key()``.
``binary_blob_logic`` is the logic handling of blob. For most of blob (like CUDA, OpenCL), we will call
``SaveToBinary`` function to serialize blob into binary. However, like LLVM / C module, we will only write
``_lib`` to indicate this is a DSO module.
.. note::
Whether or not it is required to implement the SaveToBinary virtual function depends on
how the module is used. For example, if the module has information we need when we load
the dynamic shared library back, we should do. Like CUDA module, we need its binary data
passing to GPU driver when we load the dynamic shared library, so we should implement
``SaveToBinary`` to serialize its binary data. But for host module (like DSO), we don't
need other information when we load the dynamic shared library, so we don't need to implement
``SaveToBinary``. However, if in the future, we want to record some meta information of DSO module,
we could implement ``SaveToBinary`` for DSO module too.
Finally, we will write one key ``_import_tree`` unless our module only
has one DSO module and it is in the root. It is used to reconstruct the
module import relationship when we load the exported library back as said
before. The ``import_tree_logic`` is just to write ``import_tree_row_ptr_``
and ``import_tree_child_indices_`` into stream.
After this step, we will pack it into a symbol
``runtime::symbol::tvm_ffi_library_bin`` that can be recovered in the dynamic
library.
Now, we complete the serialization part. As you have seen, we could
support arbitrary modules to import ideally.
****************
Deserialization
****************
The entrance API is ``tvm.runtime.load_module``. This function
actually calls ``_LoadFromFile``. If we dig it a little deeper, this is
``Module::LoadFromFile``. In our example, the file is ``deploy.so``,
according to the function logic, we will call ``module.loadfile_so`` in
``dso_library.cc``. The key is here:
.. code:: c++
// Load the imported modules
const char* library_bin = reinterpret_cast<const char*>(
lib->GetSymbol(runtime::symbol::tvm_ffi_library_bin));
Module root_mod;
if (library_bin != nullptr) {
root_mod = ProcessLibraryBin(library_bin, lib);
} else {
// Only have one single DSO Module
root_mod = Module(n);
}
As said before, we will pack the blob into the symbol
``runtime::symbol::tvm_ffi_library_bin``. During deserialization part, we will
inspect it. If we have ``runtime::symbol::tvm_ffi_library_bin``, we will call ``ProcessLibraryBin``,
whose logic like this:
.. code:: c++
READ(blob_size)
READ(blob_type_key)
for (size_t i = 0; i < blob_size; i++) {
if (blob_type_key == "_lib") {
// construct dso module using lib
} else if (blob_type_key == "_import_tree") {
// READ(_import_tree_row_ptr)
// READ(_import_tree_child_indices)
} else {
// call module.loadbinary_blob_type_key, such as module.loadbinary_cuda
// to restore.
}
}
// Using _import_tree_row_ptr and _import_tree_child_indices to
// restore module import relationship. The first module is the
// root module according to our invariance as said before.
return root_module;
After this, we will set the ``ctx_address`` to be the ``root_module`` so
that allow lookup of symbol from root (so all symbols are visible).
Finally, we complete the deserialization part.
+669
View File
@@ -0,0 +1,669 @@
.. 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
+427
View File
@@ -0,0 +1,427 @@
.. 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.
.. _relax-vm-arch:
Relax Virtual Machine
=====================
This document explains the Relax VM architecture in detail, covering the compilation pipeline
from Relax IR to bytecode, the instruction set, the execution model, and the Python-level user
interface.
Overview
--------
The end-to-end flow from model to execution is:
1. **Relax IR** — a high-level computational graph (``relax.Function`` inside an ``IRModule``).
2. **Compilation**``tvm.compile()`` applies the Relax transformation pipeline, then invokes
``VMCodeGen`` to translate each Relax function into bytecode instructions.
3. **Linking** — TIR functions are compiled to native kernels (via LLVM, CUDA, etc.); the bytecode,
constant pool, and compiled kernels are packaged together into a ``VMExecutable``.
4. **Execution** — at runtime, a ``VirtualMachine`` loads the executable, initializes devices and
memory allocators, and runs the bytecode.
.. code-block:: text
IRModule (Relax + TIR)
▼ relax_pipeline (FuseOps, LegalizeOps, ...)
IRModule (optimized)
▼ VMCodeGen
ExecBuilder (bytecode) + IRModule (TIR only)
│ │
│ ▼ tirx.build()
│ runtime.Module (native kernels)
│ │
▼ VMLink ▼
VMExecutable ◄───────── linked together
▼ VirtualMachine(exec, device)
Runtime execution
Compilation: From Relax IR to Bytecode
--------------------------------------
Build entry point
~~~~~~~~~~~~~~~~~
The main entry point is ``tvm.compile()`` (which delegates to ``relax.build()`` in
``python/tvm/relax/vm_build.py``):
.. code-block:: python
import tvm
from tvm import relax
@tvm.script.ir_module
class MyModule:
@R.function
def main(x: R.Tensor((3, 4), "float32")):
return R.add(x, x)
target = tvm.target.Target("llvm")
ex = tvm.compile(MyModule, target)
Internally, ``relax.build()`` performs these steps:
1. Apply the **Relax pipeline** (``relax.get_pipeline("default")``), which includes operator
legalization, fusion, buffer planning, and other graph-level passes.
2. Create an ``ExecBuilder`` and run **VMCodeGen** (``src/relax/backend/vm/codegen_vm.cc``),
which walks each ``relax.Function`` and emits bytecode instructions. The Relax functions are
removed from the IRModule; only TIR functions remain.
3. Compile the remaining TIR functions to native code via ``tirx.build()``.
4. **Link** the bytecode executable with the compiled native module using ``VMLink``, producing
a ``VMExecutable``.
Two execution modes are supported:
- ``exec_mode="bytecode"`` (default): Relax functions are interpreted by the VM's bytecode
dispatch loop.
- ``exec_mode="compiled"``: Relax functions are compiled into TIR functions (``VMTIRCodeGen``)
that directly manipulate the register file, bypassing the interpreter loop. This avoids
dispatch overhead but produces more code.
Bytecode generation
~~~~~~~~~~~~~~~~~~~
The ``CodeGenVM`` class (``src/relax/backend/vm/codegen_vm.cc``) is an ``ExprFunctor`` that visits
each Relax expression and emits instructions through the ``ExecBuilder``:
- Each ``relax.Var`` is mapped to a register.
- Function parameters occupy registers 0 through N-1.
- Each binding in a ``SeqExpr`` generates one or more instructions; the result is stored in a
new register.
- Function calls (``R.call_tir``, ``R.call_packed``, operator calls) become ``Call`` instructions.
- Conditional expressions (``relax.If``, written as Python ``if`` in TVMScript) become an ``If``
instruction followed by ``Goto`` to skip branches.
- The function body ends with a ``Ret`` instruction.
Instruction Set
---------------
The VM uses a **register-based** architecture with an intentionally minimal instruction set.
There are only four opcodes:
.. list-table::
:header-rows: 1
:widths: 15 30 55
* - Opcode
- Fields
- Semantics
* - ``Call``
- ``dst``, ``func_idx``, ``num_args``, ``args[]``
- Call function ``func_idx`` with the given arguments; store the result in register ``dst``.
* - ``Ret``
- ``result``
- Return the value in register ``result`` to the caller.
* - ``Goto``
- ``pc_offset``
- Jump forward or backward by ``pc_offset`` instructions.
* - ``If``
- ``cond``, ``false_offset``
- If register ``cond`` is nonzero, fall through (pc++); otherwise jump by ``false_offset``.
The VM itself performs **no mathematical computation**. All actual work — matrix multiplications,
convolutions, elementwise operations — is carried out by compiled TIR kernels or external
libraries (cuBLAS, cuDNN, etc.), dispatched through ``Call`` instructions.
Instruction encoding
~~~~~~~~~~~~~~~~~~~~
Each instruction argument (``Instruction::Arg``) is a 64-bit word encoded as:
- **Bits [63:56]**``ArgKind`` (8 bits): ``kRegister`` (0), ``kImmediate`` (1), ``kConstIdx`` (2),
or ``kFuncIdx`` (3).
- **Bits [55:0]** — value (56 bits, sign-extended).
Two special register values exist:
- ``kVoidRegister``: indicates "no destination" (the return value is discarded).
- ``kVMRegister``: refers to the VM context pointer itself, passed as the first argument to
closures.
The instruction stream is stored as a flat ``vector<ExecWord>`` (``instr_data``) with an offset
table (``instr_offset``) for random access.
Executable
----------
A ``VMExecutable`` (``include/tvm/runtime/vm/executable.h``) bundles everything needed for
execution:
- **Function table** (``func_table``): a ``vector<VMFuncInfo>`` describing every function. Each
entry records the function's kind, name, instruction range (``start_instr`` to ``end_instr``),
number of arguments, register file size, and parameter names.
- **Constant pool** (``constants``): model weights, shape tuples, and other compile-time constants.
- **Bytecode** (``instr_data`` + ``instr_offset``): the instruction stream.
- **Imported modules**: the compiled TIR kernels and external libraries.
Function kinds
~~~~~~~~~~~~~~
The VM recognizes three function kinds (``VMFuncInfo::FuncKind``):
.. list-table::
:header-rows: 1
:widths: 20 80
* - Kind
- Description
* - ``kPackedFunc``
- An external C/C++ function looked up from imported modules or the global PackedFunc
registry. Examples: ``vm.builtin.alloc_shape_heap``, ``vm.builtin.match_shape``.
* - ``kVMFunc``
- A bytecode-interpreted Relax function. The VM interprets its instructions in ``RunLoop()``.
* - ``kVMTIRFunc``
- A Relax function compiled to a TIR function (``exec_mode="compiled"``). Found in
imports under the name ``__vmtir__<func_name>``. Called directly with register file
pointers, bypassing the interpreter loop.
Serialization
~~~~~~~~~~~~~
The executable supports binary serialization for deployment:
.. code-block:: python
# Save
ex.export_library("model.so")
# Load
loaded = tvm.runtime.load_module("model.so")
vm = relax.VirtualMachine(loaded, tvm.cuda())
The binary format includes a magic number (``0xD225DE2F4214151E``), a version string
(currently ``"0.14"``), followed by four sections: globals (the function table), memory scopes,
constant pool, and bytecode. ``AsText()`` and ``AsPython()`` provide human-readable representations
for debugging.
Runtime Execution
-----------------
VM initialization
~~~~~~~~~~~~~~~~~
At runtime, a ``VirtualMachine`` is created and initialized:
.. code-block:: python
from tvm.relax import VirtualMachine
vm = VirtualMachine(exec_module, tvm.cuda())
Under the hood:
1. **LoadExecutable**: the bytecode and metadata are loaded from the ``VMExecutable``.
2. **Init**: devices and memory allocators are set up. Each device gets an ``Allocator``
(either ``NAIVE_ALLOCATOR`` or ``POOLED_ALLOCATOR``, defaulting to pooled). A CPU device
is always added for shape computations.
3. **InitFuncPool**: the function pool is populated — ``kPackedFunc`` entries are resolved from
imports or the global registry; ``kVMFunc`` and ``kVMTIRFunc`` entries are wrapped in
``VMClosure`` objects.
4. **Constant pool**: model constants are loaded and optionally transferred to the target device.
The bytecode dispatch loop
~~~~~~~~~~~~~~~~~~~~~~~~~~
When a ``kVMFunc`` is invoked, the VM enters ``InvokeBytecode()``:
1. A new ``VMFrame`` is pushed onto the call stack. Each frame contains:
- A **register file** (``vector<ffi::Any>``) — type-erased slots that can hold tensors,
shapes, closures, or any TVM object. The size is determined at compile time
(``VMFuncInfo::register_file_size``).
- The **return program counter** — where to resume after the function returns.
- The **caller's return register** — which register in the parent frame receives the result.
2. Function arguments are written to registers 0..N-1.
3. The program counter (``pc_``) is set to the function's ``start_instr``.
4. ``RunLoop()`` executes instructions until a ``Ret`` is encountered:
- **Call**: resolve arguments (from registers, immediates, constant pool, or function pool),
invoke the target function via ``InvokeClosurePacked()``, store the result in ``dst``.
- **Ret**: read the return value from the specified register, write the result to the
caller's return register, and return from ``RunLoop()`` (the frame is popped by an RAII
guard when ``InvokeBytecode()`` exits).
- **Goto**: adjust ``pc_`` by the offset.
- **If**: check the condition register; if nonzero, fall through; otherwise jump by
``false_offset``.
The dispatch loop is implemented in ``src/runtime/vm/vm.cc`` (``VirtualMachineImpl::RunLoop``).
.. code-block:: text
Frame Stack Register File (per frame)
┌─────────────┐ ┌────┬────┬────┬─────┬────┐
│ Frame 2 │ ───────► │ R0 │ R1 │ R2 │ ... │ Rn │
├─────────────┤ └────┴────┴────┴─────┴────┘
│ Frame 1 │ ───────► [register file]
├─────────────┤
│ Frame 0 │ ───────► [register file]
└─────────────┘
VMClosure and function dispatch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Functions in the VM are stored in a ``func_pool_`` indexed by function table position.
``kVMFunc`` and ``kVMTIRFunc`` entries are wrapped as ``VMClosure`` objects, while ``kPackedFunc``
entries are stored as plain ``ffi::Function``. A ``VMClosure`` stores:
- ``func_name``: the function's string name.
- ``impl``: a ``ffi::Function`` that takes the VM context pointer as its first argument, followed
by the actual parameters.
When the VM encounters a ``Call`` instruction, it looks up the function in ``func_pool_`` by
index and dispatches via ``InvokeClosurePacked()``. If the target is a ``VMClosure``, the VM
pointer is prepended to the arguments and ``impl`` is invoked. If it is a plain
``ffi::Function``, it is called directly.
``VMClosure::BindLastArgs`` enables partial application — it creates a new function with
some arguments pre-bound at the end, useful for implementing captured closures in Relax.
Built-in operations
~~~~~~~~~~~~~~~~~~~
The VM relies on several built-in PackedFuncs (registered in ``src/runtime/vm/builtin.cc``)
for runtime support:
- ``vm.builtin.alloc_shape_heap``: allocate workspace for symbolic shape computations.
- ``vm.builtin.match_shape``: validate tensor shapes against expected patterns at runtime,
supporting assertions (``kAssertEqualToImm``, ``kAssertEqualToLoad``), storing symbolic
dimensions to the shape heap (``kStoreToHeap``), or no-ops (``kNoOp``).
- ``vm.builtin.make_shape``: construct shape tuples from immediates or heap-loaded values.
- ``vm.builtin.match_prim_value``: validate primitive values (e.g., integers) against expected
patterns.
- ``vm.builtin.copy``: copy a value into a register. Used in several codegen scenarios:
materializing non-register arguments (immediates, constants) into registers, ensuring each
variable binding gets its own register, and merging results from if/else branches.
Python Interface
----------------
Users interact with the VM through ``tvm.relax.VirtualMachine``:
.. code-block:: python
import tvm
from tvm import relax
import numpy as np
# Compile
ex = tvm.compile(MyModule, target="llvm")
# Create VM
vm = relax.VirtualMachine(ex, tvm.cpu())
# Direct invocation
inp = tvm.runtime.tensor(np.random.rand(3, 4).astype("float32"))
result = vm["main"](inp)
# Stateful interface (useful for RPC)
vm.set_input("main", inp)
vm.invoke_stateful("main")
output = vm.get_outputs("main")
Key methods:
- ``vm["func_name"](*args)`` — direct invocation, returns the result.
- ``vm.set_input()`` / ``vm.invoke_stateful()`` / ``vm.get_outputs()`` — stateful interface
that avoids sending output over the wire, useful for RPC-based remote execution.
- ``vm.save_function(func_name, saved_name, *args)`` — pre-bind arguments for repeated calls,
reducing dictionary lookup overhead during benchmarking.
- ``vm.time_evaluator(func_name, dev)`` — returns a timing function following the same convention
as ``tvm.runtime.Module.time_evaluator``.
- ``vm.set_instrument(func)`` — register an instrumentation callback that is invoked before/after
every ``Call`` instruction. The callback can return ``VMInstrumentReturnKind.SKIP_RUN`` to
skip the call.
Instrumentation
~~~~~~~~~~~~~~~
The VM supports observability via instrumentation:
**Instrumentation** via ``set_instrument()``:
.. code-block:: python
def my_instrument(func, func_symbol, before_run, ret_value, *args):
if before_run:
print(f"About to call: {func_symbol}")
return VMInstrumentReturnKind.NO_OP
vm.set_instrument(my_instrument)
vm["main"](inp)
The instrument function is called before and after every ``Call`` instruction, receiving the
function object, its symbol name, a flag indicating before/after, the return value (only valid
after), and all arguments.
Inspecting Bytecode
-------------------
The executable provides text and Python representations of the compiled bytecode:
.. code-block:: python
ex = tvm.compile(MyModule, target="llvm")
print(ex.as_text()) # Human-readable instruction listing
print(ex.as_python()) # Equivalent Python program
print(ex.stats()) # Summary statistics
These are invaluable for debugging compilation issues — they show exactly which functions
are called, in what order, and how registers are used.
Source Code Map
---------------
.. list-table::
:header-rows: 1
:widths: 45 55
* - Path
- Contents
* - ``include/tvm/runtime/vm/bytecode.h``
- Instruction, Opcode, and Arg definitions
* - ``include/tvm/runtime/vm/executable.h``
- VMExecutable, VMFuncInfo, serialization
* - ``include/tvm/runtime/vm/vm.h``
- VirtualMachine base class, VMClosure
* - ``src/runtime/vm/vm.cc``
- VirtualMachineImpl, RunLoop, InvokeBytecode
* - ``src/runtime/vm/executable.cc``
- Serialization/deserialization, text output
* - ``src/runtime/vm/builtin.cc``
- Built-in operations (shape matching, allocation)
* - ``src/relax/backend/vm/codegen_vm.cc``
- CodeGenVM: Relax IR → bytecode
* - ``src/relax/backend/vm/codegen_vm_tir.cc``
- VMTIRCodeGen: Relax IR → compiled TIR
* - ``python/tvm/runtime/vm.py``
- Python VirtualMachine wrapper
* - ``python/tvm/relax/vm_build.py``
- ``relax.build()`` and VMExecutable Python class
+281
View File
@@ -0,0 +1,281 @@
.. 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.
.. _tvm-runtime-system:
TVM Runtime System
==================
TVM supports multiple programming languages for the compiler stack development and deployment.
In this note, we explain the key elements of the TVM runtime.
.. image:: https://tvm.apache.org/images/release/tvm_flexible.png
We need to satisfy quite a few interesting requirements:
- Deployment: invoke the compiled function from python/javascript/c++ language.
- Debug: define a function in python and call that from a compiled function.
- Link: write driver code to call device specific code (CUDA) and call it from compiled host function.
- Prototype: define an IR pass from python and call that from C++ backend.
- Expose: compiler stack developed in c++ to front-end (i.e, python)
- Experiment: ship a compiled function to an embedded device to directly run there.
We want to be able to define a function from any language and call from another.
We also want the runtime core to be minimal to deploy to embedded devices.
.. _tvm-runtime-system-packed-func:
PackedFunc
----------
`PackedFunc`_ is a simple but elegant solution we find to solve the
challenges listed. A single ``PackedFunc`` object represents a
function call whose caller and callee may be in different languages.
The following code block provides an example in C++
.. _PackedFunc: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/function.h
.. code:: c
#include <tvm/ffi/function.h>
void MyAdd(ffi::PackedArgs args, ffi::Any* rv) {
// automatically convert arguments to desired type.
int a = args[0].cast<int>();
int b = args[1].cast<int>();
// automatically assign value return to rv
*rv = a + b;
}
void CallPacked() {
PackedFunc myadd = PackedFunc(MyAdd);
// get back 3
int c = myadd(1, 2);
}
In the above codeblock, we defined a PackedFunc MyAdd. It takes two arguments
: ``args`` represents input arguments and ``rv`` represents return value.
The function is type-erased, which means that the function signature does not restrict which input type to pass in or type to return.
Under the hood, when we call a PackedFunc, it packs the input arguments to ffi::PackedArgs on stack,
and gets the result back via ffi::Any.
Thanks to template tricks in C++, we can call a PackedFunc just like a normal function. Because of its type-erased nature, we can call a PackedFunc from dynamic languages like python, without additional glue code for each new type function created.
The following example registers PackedFunc in C++ and calls from python.
.. code:: c
// register a global packed function in c++
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def_packed("myadd", MyAdd);
}
.. code:: python
import tvm
myadd = tvm.get_global_func("myadd")
# prints 3
print(myadd(1, 2))
Most of the magic of PackedFunc lies in ``ffi::PackedArgs`` and ``ffi::Any`` structure.
We restrict a list of possible types which can be passed.
Here are the common ones:
- int, float and string
- PackedFunc itself
- Module for compiled modules
- DLTensor* for tensor object exchange
- TVM Object to represent any object in IR
The restriction makes the implementation simple without the need of serialization.
Despite being minimum, the PackedFunc is sufficient for the use-case of deep learning deployment as
most functions only take DLTensor or numbers.
Since one PackedFunc can take another PackedFunc as an argument,
we can pass functions from python (as PackedFunc) to C++.
.. code:: c
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def_packed("callhello", [](ffi::PackedArgs args, ffi::Any* rv) {
ffi::Function f = args[0].cast<ffi::Function>();
f("hello world");
});
}
.. code:: python
import tvm
def callback(msg):
print(msg)
# convert to PackedFunc
f = tvm.runtime.convert(callback)
callhello = tvm.get_global_func("callhello")
# prints hello world
callhello(f)
TVM provides a `minimum C API`_,
which allows us to embed the PackedFunc into any languages. Besides python, so far we supported
`java`_ and `javascript`_.
This philosophy of embedded API is very like Lua, except that we don't have a new language but use C++.
.. _minimum C API: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/c_api.h
.. _java: https://github.com/apache/tvm/tree/main/jvm
.. _javascript: https://github.com/apache/tvm/tree/main/web
One fun fact about PackedFunc is that we use it for both compiler and deployment stack.
- All compiler pass functions of TVM are exposed to frontend as PackedFunc
- The compiled module also returns the compiled function as PackedFunc
To keep the runtime minimum, we isolated the IR Object support from the deployment runtime. The resulting runtime takes around 200K - 600K depending on how many runtime driver modules (e.g., CUDA) get included.
The overhead of calling into PackedFunc vs. a normal function is small, as it is only saving a few values on the stack.
So it is OK as long as we don't wrap small functions.
In summary, the PackedFunc is the universal glue in TVM where we use it extensively to support our compiler and deployment.
.. _tvm-runtime-system-module:
Module
------
Since TVM supports multiple types of devices, we need to support different type of drivers.
We have to use the driver API to load the kernel, set up the argument in packed format and perform kernel launch.
We also need to patch up the driver API so that the exposed functions are threadsafe.
So we often need to implement these driver glues in C++ and expose them to the user.
We can certainly not do it for each type of functions, so again PackedFunc is our answer.
TVM defines the compiled object as `Module`_.
The user can get the compiled function from Module as PackedFunc.
The generated compiled code can dynamically get function from Module in runtime. It caches the function handle in the first call and reuses in subsequent calls. We use this to link device code and callback into any PackedFunc(e.g., python) from generated code.
.. _Module: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/extra/module.h
The ModuleNode is an abstract class that can be implemented by each type of device.
So far we support modules for CUDA, Metal, OpenCL and loading dynamic shared libraries. This abstraction makes introduction
of new device easy, and we do not need to redo the host code generation for each type of device.
Remote Deployment
-----------------
The PackedFunc and Module system also makes it easy to ship the function into remote devices directly.
Under the hood, we have an RPCModule that serializes the arguments to do the data movement and launches the computation on the remote.
.. image:: https://tvm.apache.org/images/release/tvm_rpc.png
The RPC server itself is minimum and can be bundled into the runtime. We can start a minimum TVM
RPC server on iPhone/android/raspberry pi or even the browser. The cross compilation on server and shipping of the module for testing can be done in the same script. Checkout
:ref:`tutorial-cross-compilation-and-rpc` for more details.
This instant feedback gives us a lot of advantages. For example, to test the correctness of generated code on iPhone, we no longer have to write test-cases in swift/objective-c from scratch -- We can use RPC to execute on iPhone, copy the result back and do verification on the host via numpy. We can also do the profiling using the same script.
TVM Object and Compiler Stack
-----------------------------
As we mentioned earlier, we build compiler stack API on top of the PackedFunc runtime system.
We faced a constant changing of the compiler API for the need of research. We need a new language object or IR node whenever we want to test out new primitives.
However, we don't want to change our API from time to time. Besides that, we also want to
- be able to serialize any language object and IRs
- be able to explore, print, and manipulate the IR objects in front-end language to do quick prototyping.
We introduced a base class, called `Object`_ to solve this problem.
All the language object in the compiler stack is a subclass of ``Object``. Each object contains a string type_key that uniquely identifies
the type of object. We choose string instead of int as type key so new ``Object`` class can be added in the decentralized fashion without
adding the code back to the central repo. To ease the speed of dispatching, we allocate an integer type_index at runtime for each type_key.
.. _Object: https://github.com/apache/tvm/blob/main/include/tvm/runtime/object.h
Since usually one ``Object`` could be referenced in multiple places in the language, we use a shared_ptr to keep
track of reference. We use ``ObjectRef`` class to represent a reference to the ``Object``.
We can roughly view ``ObjectRef`` class as shared_ptr to the ``Object`` container.
We can also define subclass ``ObjectRef`` to hold each subtypes of ``Object``. Each subclass of ``Object`` needs to define the
RegisterReflection function.
Each ``Object`` subclass will override this to register its members. Here is an example implementation of IntImmNode.
.. code:: c
class IntImmNode : public PrimExprNode {
public:
/*! \brief the Internal value. */
int64_t value;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IntImmNode>().def_ro("value", &IntImmNode::value);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.IntImm", IntImmNode, PrimExprNode);
};
// in cc file
TVM_FFI_STATIC_INIT_BLOCK() { IntImmNode::RegisterReflection(); }
The RegisterReflection gives us a reflection API to register each member of the object.
We can use this function to visit the node and serialize any language object recursively.
It also allows us to get members of an object easily in front-end language.
For example, we can access the value field of the IntImmNode.
.. code:: python
import tvm
x = tvm.tirx.IntImm("int32", 1)
# access the value field of IntImmNode
print(x.value)
New ``Object`` can be added to C++ without changing the front-end runtime, making it easy to make extensions to the compiler stack.
Note that this is not the fastest way to expose members to front-end language, but might be one of the simplest
approaches possible. We also find that it fits our purposes as we mainly use python for testing and prototyping and still use c++
to do the heavy lifting job.
Implementation Details
----------------------
Each argument in PackedFunc contains a union value `TVMValue`_
and a type code. This design allows the dynamically typed language to convert to the corresponding type directly, and statically typed language to
do runtime type checking during conversion.
.. _TVMValue: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/c_api.h
The relevant files are
- `function.h`_ for C++ PackedFunc API
- `c_api.h`_ for C API.
.. _function.h: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/function.h
.. _c_api.h: https://github.com/apache/tvm/blob/main/3rdparty/tvm-ffi/include/tvm/ffi/c_api.h
To support extension types, we used a registry system to register type related information, like support of any
in C++. See the ``tvm-ffi`` subproject under ``3rdparty/tvm-ffi/`` for more details on the FFI type system.
Runtime-Specific Information
============================
.. toctree::
:maxdepth: 1
:glob:
runtimes/*
+259
View File
@@ -0,0 +1,259 @@
.. 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.
.. _tvm-runtime-vulkan:
Vulkan Runtime
==============
TVM supports using Vulkan compute shaders to execute queries. Each
computational kernel is compiled into a SPIR-V shader, which can then
be called using the TVM interface.
.. _tvm-runtime-vulkan-features:
Vulkan Features, Limits
-----------------------
.. _Required Limits: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#limits-minmax
Since different Vulkan implementations may enable different optional
features or have different physical limits, the code generation must
know which features are available to use. These correspond to
specific Vulkan capabilities/limits as in
:ref:`Vulkan Capabilities Table <tvm-table-vulkan-capabilities>`.
If unspecified, TVM assumes that a capability is not available, or
that a limit is the minimum guaranteed by the Vulkan spec in the
`Required Limits`_ section.
These parameters can be either explicitly specific when defining a
:ref:`Target <tvm-target-specific-target>`, or can be queried from a
device. To query from a device, the special parameter
``-from_device=N`` can be used to query all vulkan device parameters
from device id ``N``. Any additional parameters explicitly specified
will override the parameters queried from the device.
.. _VkSubgroupFeatureFlagBits: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSubgroupFeatureFlagBits.html
.. list-table:: Vulkan Capabilities
:name: tvm-runtime-table-vulkan-capabilities
:header-rows: 1
* - Target Parameter
- Required Vulkan Version/Extension
- Parameter Queried
- Default Value
* - ``supported_subgroup_operations``
- Vulkan 1.1+
- ``VkPhysicalDeviceSubgroupProperties::supportedOperations``
- 0 (interpreted as `VkSubgroupFeatureFlagBits`_)
* - ``max_push_constants_size``
-
- ``VkPhysicalDeviceLimits::maxPushConstantsSize``
- 128 bytes
* - ``max_uniform_buffer_range``
-
- ``VkPhysicalDeviceLimits::maxUniformBufferRange``
- 16384 bytes
* - ``max_storage_buffer_range``
-
- ``VkPhysicalDeviceLimits::maxStorageBufferRange``
- 2\ :sup:`27`\ bytes
* - ``max_per_stage_descriptor_storage_buffer``
-
- ``VkPhysicalDeviceLimits::maxPerStageDescriptorStorageBuffers``
- 4
* - ``supports_storage_buffer_storage_class``
- VK_KHR_storage_buffer_storage_class
-
- false
* - ``supports_storage_buffer_8bit_access``
- VK_KHR_8bit_storage
- ``VkPhysicalDevice8BitStorageFeaturesKHR::storageBuffer8BitAccess``
- false
* - ``supports_storage_buffer_16bit_access``
- VK_KHR_16bit_storage
- ``VkPhysicalDevice16BitStorageFeaturesKHR::storageBuffer16BitAccess``
- false
* - ``supports_float16``
- VK_KHR_shader_float16_int8
- ``VkPhysicalDeviceShaderFloat16Int8FeaturesKHR::shaderFloat16``
- false
* - ``supports_float64``
-
- ``VkPhysicalDeviceFeatures::shaderFloat64``
- false
* - ``supports_int8``
- VK_KHR_shader_float16_int8
- ``VkPhysicalDeviceShaderFloat16Int8FeaturesKHR::shaderInt8``
- false
* - ``supports_int16``
-
- ``VkPhysicalDeviceFeatures::shaderInt16``
- false
* - ``supports_int64``
-
- ``VkPhysicalDeviceFeatures::shaderInt64``
- false
As of May 2021, not all Vulkan implementations are supported. For
example, support for 64-bit integers is required. If a Vulkan target
is not supported, an error message should be issued during SPIR-V code
generation. Efforts are also underway to remove these requirements
and support additional Vulkan implementations.
.. _tvm-runtime-vulkan-spirv-capabilities:
SPIR-V Capabilities
-------------------
Some of the device-specific capabilities also correspond to SPIR-V
capabilities or extensions that must be declared in the shader, or a
minimum SPIR-V version required in order to use a feature. The
TVM-generated shaders will declare the minimum set of
extensions/capabilities and the minimum allowed version of SPIR-V
that are needed to execute the compiled graph.
If the shader generation requires a capability or extension that is
not enabled in the ``Target``, an exception will be raised.
.. list-table:: Vulkan Capabilities
:name: tvm-table-vulkan-capabilities
:header-rows: 1
* - Target Parameter
- Required SPIR-V Version/Extension
- Declared Capability
* - ``supported_subgroup_operations``
- SPIR-V 1.3+
- Varies, see `VkSubgroupFeatureFlagBits`_
* - ``supports_storage_buffer_storage_class``
- SPV_KHR_storage_buffer_storage_class
-
* - ``supports_storage_buffer_8bit_access``
- SPV_KHR_8bit_storage
- StorageBuffer8BitAccess
* - ``supports_storage_buffer_16bit_access``
- SPV_KHR_16bit_storage
- StorageBuffer16BitAccess
* - ``supports_float16``
-
- Float16
* - ``supports_float64``
-
- Float64
* - ``supports_int8``
-
- Int8
* - ``supports_int16``
-
- Int16
* - ``supports_int64``
-
- Int64
Vulkan-Specific Environment Variables
-------------------------------------
Both the SPIR-V code generation and the Vulkan runtime have
environment variables that can modify some of the runtime behavior.
These are intended for debugging purposes, both to more easily test
specific code paths, and to output more information as needed. All
boolean flags are true if the environment variable is set to a
non-zero integer. An unset variable, the integer zero, or an empty
string are all false boolean flags.
.. _VK_KHR_push_descriptor: https://khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_push_descriptor.html
.. _VK_KHR_descriptor_update_template: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_descriptor_update_template.html
.. _VK_KHR_dedicated_allocation: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_dedicated_allocation.html
.. _VkMemoryDedicatedRequirements: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkMemoryDedicatedRequirements.html
.. _Vulkan validation layers: https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/blob/master/layers/README.md
.. _spvValidate: https://github.com/KhronosGroup/SPIRV-Tools#validator
* ``TVM_VULKAN_DISABLE_PUSH_DESCRIPTOR`` - A boolean flag. If true,
TVM will explicitly allocate descriptors, and will not use the
`VK_KHR_push_descriptor`_ or `VK_KHR_descriptor_update_template`_
extensions. If false, TVM will decide whether to use these
extensions based on their availability.
* ``TVM_VULKAN_DISABLE_DEDICATED_ALLOCATION`` - A boolean flag. If
true, TVM will not mark memory allocations as being dedicated
allocations, and will not use the `VK_KHR_dedicated_allocation`_
extension. If false, TVM will decide whether memory allocations
should be marked as dedicated based on the
`VkMemoryDedicatedRequirements`_ for that buffer.
* ``TVM_VULKAN_ENABLE_VALIDATION_LAYERS`` - A boolean flag. If true,
TVM will enable `Vulkan validation layers`_ that the device
supports. If false, no validation layers are enabled.
* ``TVM_VULKAN_DISABLE_SHADER_VALIDATION`` - A boolean flag. If true,
the SPIR-V shader validation done with `spvValidate`_ is skipped.
If false (default), all SPIR-V shaders generated by TVM are
validated with `spvValidate`_.
* ``TVM_VULKAN_DEBUG_SHADER_SAVEPATH`` - A path to a directory. If
set to a non-empty string, the Vulkan codegen will save TIR, binary
SPIR-V, and disassembled SPIR-V shaders to this directory, to be
used for debugging purposes.
+575
View File
@@ -0,0 +1,575 @@
.. 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.
.. _tvmscript-arch:
TVMScript
=========
TVMScript is a Python-based domain-specific language (DSL) for writing TVM IR. It lets users
define ``IRModule``\ s — containing both Relax functions and TIR ``PrimFunc``\ s — using
familiar Python syntax. Although TVMScript *looks* like Python, it is **not executed by the
Python interpreter**. Instead, Python decorators extract the AST from the source code and
transform it into TVM IR through a dedicated parser and IR builder pipeline.
TVMScript serves two roles in the TVM stack:
- **Authoring**: users write TIR kernels and Relax programs directly in TVMScript.
- **Roundtrip**: every ``IRModule`` can be printed back to TVMScript via ``mod.script()`` and
re-parsed to produce an equivalent module. This makes TVMScript the primary tool for
inspecting, debugging, and serializing IR.
Overview
--------
The TVMScript system has three components:
.. code-block:: text
Parsing (Python source → TVM IR):
Python source (TVMScript)
▼ ast.parse + convert
Doc AST (mirror of Python AST)
▼ Parser (dispatch by token: ir / tirx / relax)
▼ IR Builder (frame stack)
TVM IR (IRModule, PrimFunc, relax.Function)
Printing (TVM IR → Python source):
TVM IR
▼ IRDocsifier (C++, dispatch by token + type)
Doc tree (ExprDoc, StmtDoc, ...)
▼ DocToPythonScript
TVMScript text
- **Parser** (Python): reads Python source, converts it to a ``Doc AST`` (a mirror of
Python's ``ast`` module), then walks the tree using dialect-specific handlers that call
into the IR builder.
- **IR Builder** (Python + C++): provides a frame-stack API where each ``with`` block or
decorator pushes a frame. When the frame exits, the constructed IR is finalized. The builder
is shared across dialects — TIR and Relax each register their own frame types.
- **Printer** (C++): converts TVM IR objects to a ``Doc`` tree (an intermediate representation
of Python syntax), then formats the tree into valid TVMScript text.
Decorators
----------
TVMScript uses three import aliases by convention:
.. code-block:: python
from tvm.script import ir as I # module-level constructs
from tvm.script import tirx as T # TIR constructs
from tvm.script import relax as R # Relax constructs
The primary decorators are:
- ``@I.ir_module``: marks a Python class as an ``IRModule``. Each method inside becomes a
function in the module.
- ``@T.prim_func``: marks a function as a TIR ``PrimFunc``.
- ``@R.function``: marks a function as a ``relax.Function``.
These can be composed:
.. code-block:: python
@I.ir_module
class MyModule:
@T.prim_func
def add_kernel(A: T.Buffer((128,), "float32"),
B: T.Buffer((128,), "float32"),
C: T.Buffer((128,), "float32")):
for i in range(128):
with T.sblock("compute"):
vi = T.axis.spatial(128, i)
C[vi] = A[vi] + B[vi]
@R.function
def main(x: R.Tensor((128,), "float32"),
y: R.Tensor((128,), "float32")) -> R.Tensor((128,), "float32"):
with R.dataflow():
out = R.call_tir(cls.add_kernel, (x, y),
out_ty=R.Tensor((128,), "float32"))
R.output(out)
return out
When Python encounters ``@I.ir_module``, the decorator does **not** execute the class body.
Instead, it calls ``tvm.script.parse()`` which extracts the source code of the class,
builds a Doc AST, and hands it to the parser.
Parser Architecture
-------------------
The parser lives in ``python/tvm/script/parser/``.
Dispatch mechanism
~~~~~~~~~~~~~~~~~~
Different IR dialects (TIR, Relax) need different handling for the same Python syntax. For
example, ``if ... else`` inside ``@T.prim_func`` creates a TIR ``If`` branch, while the same
syntax inside ``@R.function`` creates a Relax ``If`` node with different semantics.
The parser maintains a **dispatch token** stack (``["default"]`` initially). When it encounters
a decorated function, it inspects the decorator to determine the token — ``"tirx"`` for
``@T.prim_func``, ``"relax"`` for ``@R.function`` — and pushes it onto the stack.
Each AST node type is dispatched via a virtual table:
.. code-block:: text
ParseVTable[(token, node_type)] → handler function
Lookup order:
1. (current_token, node_type) e.g. ("tirx", "For")
2. ("default", node_type) e.g. ("default", "For")
3. generic_visit fallback
Dialect-specific parsers (``parser/tirx/parser.py``, ``parser/relax/parser.py``) register
handlers using ``@dispatch.register(token, type_name)`` decorators.
Parse flow
~~~~~~~~~~
The entry point is ``parse(program, extra_vars)``:
1. **Source extraction**: the program's source code is extracted (from a class, function, or
string) and converted to a Doc AST via Python's ``ast`` module.
2. **AST walking**: the ``Parser`` (a subclass of ``doc.NodeVisitor``) walks the Doc AST.
For each node, it looks up the handler in the dispatch table.
3. **Expression evaluation**: expressions like ``T.grid(128, 128)`` are evaluated by the
``ExprEvaluator``, which resolves names against the variable table and the ``T.``/``R.``
module namespaces.
4. **Value binding**: assignment statements (``A = T.match_buffer(...)`` in TIR,
``lv = R.add(x, y)`` in Relax) go through dialect-specific ``bind_*_value()`` functions
that register the resulting TVM objects in the parser's ``VarTable``.
5. **Scoping**: the ``VarTable`` maintains a stack of frames. Entering a ``with`` block,
``for`` loop, or function body pushes a new frame; exiting pops it. This ensures variables
are scoped correctly.
Variable table
~~~~~~~~~~~~~~
The ``VarTable`` is the parser's symbol table:
.. code-block:: text
VarTable
├── frames: [VarTableFrame, ...] ← stack of scopes
└── name2value: {str: [Any, ...]} ← name → value stack (for shadowing)
When a name is looked up, the most recent binding wins. When a frame is popped, all bindings
introduced in that frame are removed.
IR Builder Architecture
-----------------------
The IR builder (``python/tvm/script/ir_builder/``, backed by C++ in ``src/script/ir_builder/``)
provides a frame-stack API for constructing IR incrementally.
Frame stack
~~~~~~~~~~~
The core idea: each IR scope (module, function, block, loop) is a **frame**. Frames are pushed
on ``__enter__`` and popped on ``__exit__``. When a frame exits, it finalizes the IR it
represents and attaches it to the parent frame.
.. code-block:: text
IRBuilder (thread-local singleton)
└── frame stack:
├── IRModuleFrame ← @I.ir_module
│ ├── PrimFuncFrame ← @T.prim_func
│ │ ├── ForFrame ← T.grid(...) / T.serial(...)
│ │ │ └── SBlockFrame ← T.sblock(...)
│ │ └── ...
│ └── FunctionFrame ← @R.function
│ └── BindingBlockFrame ← R.dataflow()
└── ...
This design means the parser never needs to build a complete IR tree in memory — it
constructs IR top-down by entering and exiting frames, and each frame handles its own
finalization.
TIR builder
~~~~~~~~~~~
The TIR builder (``ir_builder/tirx/ir.py``) provides functions that map directly to TVMScript
syntax. Key categories:
**Function and block**:
- ``T.prim_func()````PrimFuncFrame``
- ``T.sblock(name)````SBlockFrame`` (spatial block)
- ``T.init()````BlockInitFrame`` (reduction initialization)
- ``T.reads(...)``, ``T.writes(...)`` → declare buffer access regions
**Loops**:
- ``T.grid(*extents)````ForFrame`` returning loop variables
- ``T.serial(start, stop)``, ``T.parallel(...)``, ``T.vectorized(...)``,
``T.unroll(...)``, ``T.thread_binding(...)`` → loop with specific iterator type
**Block axes**:
- ``T.axis.spatial(dom, binding)`` — spatial iteration axis
- ``T.axis.reduce(dom, binding)`` — reduction axis
- ``T.axis.remap(kinds, bindings)`` — shorthand for multiple axes
**Buffers**:
- ``T.match_buffer(param, shape, dtype)`` — match function parameter to buffer
- ``T.alloc_buffer(shape, dtype)`` — allocate intermediate buffer
- ``T.Buffer(shape, dtype)`` — buffer type annotation in function signatures
Relax builder
~~~~~~~~~~~~~
The Relax builder (``ir_builder/relax/ir.py``) provides:
**Function and dataflow**:
- ``R.function()````FunctionFrame``
- ``R.dataflow()````BindingBlockFrame``
- ``R.output(*vars)`` → expose variables from a dataflow block
**Emit**:
- ``R.emit(value)`` → emit a binding, returns a ``Var``
- ``R.emit_match_cast(value, ty)`` → emit with type assertion
**Type annotations**:
- ``R.Tensor(shape, dtype)`` — tensor type
- ``R.Tuple(*fields)`` — tuple type
- ``R.Shape(values)`` — shape type
- ``R.Any()`` — any Relax value type
**Calling conventions**:
- ``R.call_tir(func, args, out_ty)`` — call a TIR function
- ``R.call_packed(name, *args)`` — call a PackedFunc
- ``R.call_dps_packed(func, *args)`` — call using destination-passing style
**Operators**: the ``R`` module also re-exports all Relax operators
(``R.add``, ``R.matmul``, ``R.nn.conv2d``, etc.) so they can be used directly in TVMScript.
Printer Architecture
--------------------
The printer converts TVM IR back to TVMScript text. It is implemented primarily in C++
(``src/script/printer/``) for performance.
Doc tree
~~~~~~~~
The printer does **not** generate text directly. Instead, it first builds a ``Doc`` tree — an
intermediate representation that mirrors Python syntax:
- **Expression docs**: ``IdDoc``, ``AttrAccessDoc``, ``CallDoc``, ``IndexDoc``,
``OperationDoc``, ``LiteralDoc``, ``TupleDoc``, ``ListDoc``, etc.
- **Statement docs**: ``AssignDoc``, ``ForDoc``, ``IfDoc``, ``ScopeDoc`` (``with`` blocks),
``FunctionDoc``, ``ClassDoc``, ``ReturnDoc``, ``CommentDoc``, etc.
For example, ``T.axis.spatial(128, i)`` is represented as:
.. code-block:: text
CallDoc(
callee=AttrAccessDoc(AttrAccessDoc(IdDoc("T"), "axis"), "spatial"),
args=[LiteralDoc(128), IdDoc("i")]
)
IRDocsifier
~~~~~~~~~~~
The ``IRDocsifier`` (``include/tvm/script/printer/ir_docsifier.h``) is the main dispatcher.
It maintains:
- A dispatch table mapping ``(token, type_index)`` pairs to converter functions.
- A frame stack for tracking the current scope (similar to the builder's frame stack).
- A variable-to-name mapping to produce readable names.
Each IR dialect registers its own converters:
- ``src/script/printer/tirx/`` — converts PrimFunc, Buffer, SBlock, loops, expressions.
- ``src/script/printer/relax/`` — converts relax.Function, bindings, types, operators.
- ``src/script/printer/ir/`` — converts IRModule, shared types.
The final step calls ``DocToPythonScript()`` (``src/script/printer/doc_printer/python_doc_printer.cc``)
to format the Doc tree into properly indented Python text.
Roundtrip guarantee
~~~~~~~~~~~~~~~~~~~
For any ``IRModule`` constructed through the compiler:
.. code-block:: python
text = mod.script() # IR → TVMScript text
reparsed = tvm.script.from_source(text) # text → IR
tvm.ir.assert_structural_equal(mod, reparsed)
This roundtrip property is relied upon by testing infrastructure and serialization workflows.
Note that the printed text may differ from hand-written TVMScript — the printer uses canonical
forms (e.g., explicit ``R.emit`` calls, fully qualified buffer annotations) that are not required
in hand-written code.
Supported Python Syntax
-----------------------
TVMScript supports a subset of Python syntax. The table below summarizes what is supported
and how each construct is interpreted:
.. list-table::
:header-rows: 1
:widths: 25 15 60
* - Python Syntax
- TIR
- Relax
* - ``for i in range(n)``
- Serial loop nest
- Not supported (no Relax-level ``for`` handler)
* - ``with T.sblock(...)``
- Spatial block scope
- N/A
* - ``with R.dataflow()``
- N/A
- Dataflow block
* - ``if ... else``
- TIR ``If`` branch (PrimExpr condition) or static eval (Python bool)
- Relax ``If`` node (plain Python ``if cond:`` syntax)
* - ``while``
- ``T.While`` loop
- Not supported
* - ``x = expr``
- Variable binding
- Emit binding (implicit ``R.emit``)
* - ``x: T.Buffer(...)``
- Buffer annotation
- N/A
* - ``x: R.Tensor(...)``
- N/A
- Struct info annotation
* - ``return``
- Not used
- Function return value
* - ``A[i, j]``
- Buffer load
- Not applicable (use operators)
* - ``A[i, j] = expr``
- Buffer store
- Not applicable
* - Arithmetic (``+``, ``-``, etc.)
- PrimExpr operations
- Calls to Relax operators
* - Function calls
- ``T.*`` intrinsics
- ``R.*`` operators or ``call_tir`` / ``call_packed``
**Not supported**: ``class`` definitions (except for ``@I.ir_module``), ``try/except``,
``yield``, ``async/await``, list comprehensions, ``lambda``, ``import``, and ``global``
statements.
TIR Syntax Reference
---------------------
Function definition
~~~~~~~~~~~~~~~~~~~
.. code-block:: python
@T.prim_func
def func_name(a: T.handle, b: T.handle):
A = T.match_buffer(a, (m, n), "float32")
B = T.match_buffer(b, (m,), "float32")
# function body
- ``T.handle`` — opaque handle parameter (matched to a buffer inside the function).
- ``T.Buffer(shape, dtype)`` — can also be used directly in the signature:
``def func(A: T.Buffer((128,), "float32"))``.
Block and axes
~~~~~~~~~~~~~~
.. code-block:: python
for i, j in T.grid(128, 128):
with T.sblock("block_name"):
vi = T.axis.spatial(128, i)
vj = T.axis.reduce(128, j)
T.reads(A[vi, vj])
T.writes(B[vi])
# compute
- ``T.axis.spatial`` / ``T.axis.reduce`` / ``T.axis.scan`` — declare axis variables with
their iteration domain and binding to outer loop variables.
- ``T.axis.remap("SR", [i, j])`` — shorthand: ``S`` = spatial, ``R`` = reduce.
- ``T.reads(...)``, ``T.writes(...)`` — declare buffer regions accessed by this block.
Loop types
~~~~~~~~~~
.. code-block:: python
for i in T.serial(0, 128): # sequential
for i in T.parallel(0, 128): # parallel
for i in T.vectorized(0, 128): # vectorized
for i in T.unroll(0, 128): # unrolled
for i in T.thread_binding(0, 128, thread="threadIdx.x"): # GPU thread
Buffer operations
~~~~~~~~~~~~~~~~~
.. code-block:: python
C = T.alloc_buffer((128, 128), "float32") # intermediate buffer
val = A[i, j] # buffer load
B[i] = val + 1.0 # buffer store
Common intrinsics
~~~~~~~~~~~~~~~~~
.. code-block:: python
T.exp(x), T.log(x), T.sqrt(x), T.tanh(x), ... # math functions
T.cast(x, "float16") # type cast
T.if_then_else(cond, true_val, false_val) # conditional expression
T.min(a, b), T.max(a, b) # min/max
T.call_extern("func_name", *args) # external function call
T.call_packed("func_name", *args) # packed function call
T.tvm_storage_sync("shared") # GPU memory fence
Relax Syntax Reference
-----------------------
Function definition
~~~~~~~~~~~~~~~~~~~
.. code-block:: python
@R.function
def main(x: R.Tensor((128, 128), "float32"),
y: R.Tensor((128,), "float32")) -> R.Tensor((128, 128), "float32"):
# function body
return result
- ``R.Tensor(shape, dtype)`` — tensor type annotation.
- ``R.Tuple(...)``, ``R.Shape(...)``, ``R.Any()`` — other Relax type annotations.
- ``R.function(private=True)`` — marks the function as module-private.
- ``R.function(pure=False)`` — marks the function as having side effects.
Dataflow blocks
~~~~~~~~~~~~~~~
.. code-block:: python
with R.dataflow():
lv0 = R.add(x, y)
lv1 = R.nn.relu(lv0)
R.output(lv1)
Variables inside a ``R.dataflow()`` block are local to that block. ``R.output(...)`` exposes
variables to the outer scope.
Calling TIR functions
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
out = R.call_tir(cls.my_kernel, (x, y), out_ty=R.Tensor((128,), "float32"))
- ``cls.my_kernel`` — references a TIR ``PrimFunc`` in the same module.
- ``out_ty`` — the type (shape and dtype) of the output tensor.
Control flow
~~~~~~~~~~~~
Relax ``if`` uses plain Python ``if`` syntax. The condition must be a Relax variable with
boolean type. Both branches are required.
.. code-block:: python
@R.function
def f(cond: R.Tensor((), "bool"), x: R.Tensor((128,), "float32")):
if cond:
result = R.add(x, x)
else:
result = R.multiply(x, x)
return result
Source Code Map
---------------
.. list-table::
:header-rows: 1
:widths: 50 50
* - Path
- Contents
* - ``python/tvm/script/parser/core/``
- Core parser: dispatch, expression evaluator, variable table, Doc AST
* - ``python/tvm/script/parser/tirx/``
- TIR-specific parser handlers and value binding
* - ``python/tvm/script/parser/relax/``
- Relax-specific parser handlers and value binding
* - ``python/tvm/script/parser/ir/``
- ``@I.ir_module`` entry point and module-level parsing
* - ``python/tvm/script/ir_builder/base.py``
- IRBuilder base class and frame stack mechanism
* - ``python/tvm/script/ir_builder/tirx/``
- TIR frame types and builder functions (``T.*``)
* - ``python/tvm/script/ir_builder/relax/``
- Relax frame types and builder functions (``R.*``)
* - ``python/tvm/script/ir_builder/ir/``
- IRModule builder (``I.*``)
* - ``src/script/printer/``
- C++ printer: Doc tree, IRDocsifier, Python code generation
* - ``src/script/printer/tirx/``
- TIR-specific IR-to-Doc converters
* - ``src/script/printer/relax/``
- Relax-specific IR-to-Doc converters
* - ``src/script/ir_builder/``
- C++ backend for frame stack and IR construction
* - ``include/tvm/script/printer/``
- C++ headers: Doc classes, IRDocsifier, dispatch functor
* - ``include/tvm/script/ir_builder/``
- C++ headers: builder base, dialect-specific frame types
+740
View File
@@ -0,0 +1,740 @@
# 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.
# ruff: noqa: E402, E501, F401
#
# documentation build configuration file, created by
# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import gc
import inspect
import os
import re
import sys
from functools import partial
from hashlib import md5
from importlib import import_module
from pathlib import Path
from textwrap import dedent, indent
from unittest.mock import patch
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
curr_path = Path(__file__).expanduser().absolute().parent
if curr_path.name == "_staging":
# Can't use curr_path.parent, because sphinx_gallery requires a relative path.
tvm_path = Path(os.pardir, os.pardir)
else:
tvm_path = Path(os.pardir)
sys.path.insert(0, str(tvm_path.resolve() / "python"))
sys.path.insert(0, str(tvm_path.resolve() / "docs"))
# -- General configuration ------------------------------------------------
# General information about the project.
project = "tvm"
author = "Apache Software Foundation"
copyright = f"2020 - 2026, {author}"
github_doc_root = "https://github.com/apache/tvm/tree/main/docs/"
os.environ["TVM_BUILD_DOC"] = "1"
# Version information.
import tvm
from tvm import te, testing, topi
# The version is derived from the Git tag by setuptools_scm at build time and exposed
# as tvm.__version__ (see [tool.setuptools_scm] in pyproject.toml).
version = tvm.__version__
release = version
def monkey_patch(module_name, func_name):
"""Helper function for monkey-patching library functions.
Used to modify a few sphinx-gallery behaviors to make the "Open in Colab" button work correctly.
Should be called as a decorator with arguments. Note this behaves differently from unittest's
@mock.patch, as our monkey_patch decorator should be placed on the new version of the function.
"""
module = import_module(module_name)
original_func = getattr(module, func_name)
def decorator(function):
updated_func = partial(function, real_func=original_func)
setattr(module, func_name, updated_func)
return updated_func
return decorator
CURRENT_FILE_CONF = None
@monkey_patch("sphinx_gallery.py_source_parser", "split_code_and_text_blocks")
def split_code_and_text_blocks(source_file, return_node, real_func):
"""Monkey-patch split_code_and_text_blocks to expose sphinx-gallery's file-level config.
It's kinda gross, but we need access to file_conf to detect the requires_cuda flag.
"""
global CURRENT_FILE_CONF
file_conf, blocks, node = real_func(source_file, return_node)
CURRENT_FILE_CONF = file_conf
return (file_conf, blocks, node)
# This header replaces the default sphinx-gallery one in sphinx_gallery/gen_rst.py.
# Colab button has been temporarily disabled due to prebuilt packages unavailability.
COLAB_HTML_HEADER = """
.. DO NOT EDIT. THIS FILE WAS AUTOMATICALLY GENERATED BY
.. TVM'S MONKEY-PATCHED VERSION OF SPHINX-GALLERY. TO MAKE
.. CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "{python_file}"
.. only:: html
.. note::
:class: sphx-glr-download-link-note
You can click :ref:`here <sphx_glr_download_{ref_name}>` to run the Jupyter notebook locally.
.. rst-class:: sphx-glr-example-title
.. _sphx_glr_{ref_name}:
"""
# Google Colab allows opening .ipynb files on GitHub by appending a GitHub path to this base URL.
COLAB_URL_BASE = "https://colab.research.google.com/github"
# The GitHub path where the site is automatically deployed by tvm-bot.
IPYTHON_GITHUB_BASE = "apache/tvm-site/blob/asf-site/docs/_downloads/"
# The SVG image of the "Open in Colab" button.
BUTTON = (
"https://raw.githubusercontent.com/tlc-pack/web-data/main/images/utilities/colab_button.svg"
)
@monkey_patch("sphinx_gallery.gen_rst", "save_rst_example")
def save_rst_example(
example_rst, example_file, time_elapsed, memory_used, gallery_conf, language, real_func
):
"""Monkey-patch save_rst_example to customize the tutorial header.
Note: Colab button has been temporarily disabled. The colab_url and button_svg
are still generated but not used in the header template.
"""
# The url is the md5 hash of the notebook path.
example_fname = os.path.relpath(example_file, gallery_conf["src_dir"])
ref_fname = example_fname.replace(os.path.sep, "_")
notebook_path = example_fname[:-2] + "ipynb"
digest = md5(notebook_path.encode()).hexdigest()
# Fixed documentation versions must link to different (earlier) .ipynb notebooks.
# Note: colab_url is generated but not currently used in the header template.
colab_url = f"{COLAB_URL_BASE}/{IPYTHON_GITHUB_BASE}"
if "dev" not in version:
colab_url += version + "/"
colab_url += digest + "/" + os.path.basename(notebook_path)
new_header = COLAB_HTML_HEADER.format(
python_file=example_fname, ref_name=ref_fname, colab_url=colab_url, button_svg=BUTTON
)
with patch("sphinx_gallery.gen_rst.EXAMPLE_HEADER", new_header):
real_func(
example_rst, example_file, time_elapsed, memory_used, gallery_conf, language=language
)
INCLUDE_DIRECTIVE_RE = re.compile(r"^([ \t]*)\.\. include::\s*(.+)\n", flags=re.M)
COMMENT_DIRECTIVE_RE = re.compile(r"^\.\.(?: .*)?\n(?:(?: .*)?\n)*", flags=re.M)
ADMONITION_DIRECTIVE_RE = re.compile(r"^\.\. admonition:: *(.*)\n((?:(?: .*)?\n)*)\n", flags=re.M)
@monkey_patch("sphinx_gallery.notebook", "rst2md")
def rst2md(text, gallery_conf, target_dir, heading_levels, real_func):
"""Monkey-patch rst2md to support comments and some include directives.
Currently, only include directives without any parameters are supported. Also, note that in
reStructuredText any unrecognized explicit markup block is treated as a comment (see
https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#comments).
For callouts, we only replace generic "admonition" directives. All others should be replaced by
sphinx-gallery's rst2md. Note that the "alert" and "alert-info" tags are support in most IPython
notebooks, but they render kinda funky on Colab.
"""
def load_include(match):
full_path = os.path.join(target_dir, match.group(2))
with open(full_path) as f:
lines = f.read()
indented = indent(lines, match.group(1)) + "\n"
return indented
text = re.sub(INCLUDE_DIRECTIVE_RE, load_include, text)
# Replace generic, titled admonitions with indented text. Other admonitions (e.g. .. note::)
# will be handled by sphinx-gallery's
def rewrite_generic_admonition(match):
title, text = match.groups()
stripped_text = dedent(text).strip()
return f'<div class="alert alert-info"><h4>{title}</h4><p>{stripped_text}</p></div>'
text = re.sub(ADMONITION_DIRECTIVE_RE, rewrite_generic_admonition, text)
# Call the real function, and then strip any remaining directives (i.e. comments)
text = real_func(text, gallery_conf, target_dir, heading_levels)
text = re.sub(COMMENT_DIRECTIVE_RE, "", text)
return text
def install_request_hook(gallery_conf, fname):
testing.utils.install_request_hook(tvm_path.resolve() / "tests" / "python" / "request_hook.py")
INSTALL_TVM_DEV = """\
%%shell
# Installs the latest dev build of TVM from PyPI. If you wish to build
# from source, see https://tvm.apache.org/docs/install/from_source.html
pip install apache-tvm --pre"""
INSTALL_TVM_FIXED = f"""\
%%shell
# Installs TVM version {version} from PyPI. If you wish to build
# from source, see https://tvm.apache.org/docs/install/from_source.html
pip install apache-tvm=={version}"""
INSTALL_TVM_CUDA_DEV = """\
%%markdown
> **Note:** This tutorial requires a CUDA-enabled build of TVM.
> Pre-built CUDA wheels are not currently available on PyPI.
> Please build TVM from source with CUDA enabled before running this notebook:
> https://tvm.apache.org/docs/install/from_source.html"""
INSTALL_TVM_CUDA_FIXED = f"""\
%%markdown
> **Note:** This tutorial requires a CUDA-enabled build of TVM (version {version}).
> Pre-built CUDA wheels are not currently available on PyPI.
> Please build TVM from source with CUDA enabled before running this notebook:
> https://tvm.apache.org/docs/install/from_source.html"""
@monkey_patch("sphinx_gallery.gen_rst", "jupyter_notebook")
def jupyter_notebook(script_blocks, gallery_conf, target_dir, real_func):
"""Monkey-patch sphinx-gallery to add a TVM import block to each IPython notebook.
If we had only one import block, we could skip the patching and just set first_notebook_cell.
However, how we import TVM depends on if we are using a fixed or dev version, and whether we
will use the GPU.
Tutorials requiring a CUDA-enabled build of TVM should use the flag:
# sphinx_gallery_requires_cuda = True
"""
requires_cuda = CURRENT_FILE_CONF.get("requires_cuda", False)
fixed_version = "dev" not in version
if fixed_version and requires_cuda:
install_block = INSTALL_TVM_CUDA_FIXED
elif fixed_version and not requires_cuda:
install_block = INSTALL_TVM_FIXED
elif not fixed_version and requires_cuda:
install_block = INSTALL_TVM_CUDA_DEV
else:
install_block = INSTALL_TVM_DEV
new_conf = {**gallery_conf, "first_notebook_cell": install_block}
return real_func(script_blocks, new_conf, target_dir)
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
"sphinx.ext.mathjax",
"sphinx_gallery.gen_gallery",
"autodocsumm",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = [".rst", ".md"]
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# generate autosummary even if no references
autosummary_generate = True
# The main toctree document.
main_doc = "index"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build", "_staging"]
# The TIRx API pages autodoc modules (tvm.tirx, tvm.backend.cuda) that re-export
# common IR types (PrimExpr, Op, ...) from several modules, which makes a handful
# of autodoc'd cross references ambiguous. Silence that specific, benign category.
suppress_warnings = ["ref.python"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
html_theme = "sphinx_book_theme"
html_title = "Apache TVM"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_logo = "_static/img/tvm-logo-small.png"
html_favicon = "_static/img/tvm-logo-square.png"
# The Apache trademark/copyright footer is rendered through sphinx-book-theme's
# ``extra_footer`` hook (see footer_html below). This mirrors how the tvm-ffi docs
# (3rdparty/tvm-ffi/docs/conf.py) preserve the ASF menu under the book theme.
footer_dropdown = {
"name": "ASF",
"items": [
("Apache Homepage", "https://apache.org/"),
("License", "https://www.apache.org/licenses/"),
("Sponsorship", "https://www.apache.org/foundation/sponsorship.html"),
("Security", "https://tvm.apache.org/docs/reference/security.html"),
("Thanks", "https://www.apache.org/foundation/thanks.html"),
("Events", "https://www.apache.org/events/current-event"),
],
}
footer_note = " ".join(
"""
Copyright © 2026 The Apache Software Foundation. Apache TVM, Apache, the Apache feather,
and the Apache TVM project logo are either trademarks or registered trademarks of
the Apache Software Foundation.""".split("\n")
).strip()
def footer_html() -> str:
"""Build the extra footer: ASF dropdown and the Apache trademark note.
The copyright line is rendered natively by sphinx-book-theme (from the ``copyright``
config value), so it is intentionally not repeated here.
"""
dropdown_items = ""
for item_name, item_url in footer_dropdown["items"]:
dropdown_items += f'<li><a class="dropdown-item" href="{item_url}" target="_blank" style="font-size: 0.9em;">{item_name}</a></li>\n'
return f"""
<div class="footer-container" style="margin: 5px 0; font-size: 0.9em; color: #6c757d; text-align: right;">
<div class="footer-line1" style="display: flex; justify-content: flex-end; align-items: center; gap: 0.9em; margin-bottom: 3px; flex-wrap: wrap;">
<div class="footer-dropdown">
<div class="dropdown">
<button class="btn btn-link dropdown-toggle" type="button" id="footerDropdown" data-bs-toggle="dropdown"
aria-expanded="false" style="font-size: 0.9em; color: #6c757d; text-decoration: none; padding: 0; border: none; background: none;">
{footer_dropdown["name"]}
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="footerDropdown" style="font-size: 0.9em;">
{dropdown_items} </ul>
</div>
</div>
</div>
<div class="footer-line2" style="font-size: 0.9em; color: #6c757d; text-align: right;">
{footer_note}
</div>
</div>
"""
html_theme_options = {
"repository_url": "https://github.com/apache/tvm",
"repository_branch": "main",
"path_to_docs": "docs/",
"use_repository_button": True,
"use_edit_page_button": True,
"use_source_button": True,
"use_issues_button": True,
"show_toc_level": 2,
"show_navbar_depth": 1,
"icon_links": [
{
"name": "TVM Homepage",
"url": "https://tvm.apache.org/",
"icon": "fa-solid fa-house",
"type": "fontawesome",
},
{
"name": "Community",
"url": "https://tvm.apache.org/community",
"icon": "fa-solid fa-users",
"type": "fontawesome",
},
{
"name": "Download",
"url": "https://tvm.apache.org/download",
"icon": "fa-solid fa-box-open",
"type": "fontawesome",
},
],
"extra_footer": footer_html(),
}
# Output file base name for HTML help builder.
htmlhelp_basename = project + "doc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(main_doc, f"{project}.tex", project, author, "manual"),
]
intersphinx_mapping = {
"python": (f"https://docs.python.org/{sys.version_info.major}", None),
# "numpy": ("https://numpy.org/doc/stable", None),
# "scipy": ("https://docs.scipy.org/doc/scipy", None),
# "matplotlib": ("https://matplotlib.org/", None),
}
from sphinx_gallery.sorting import ExplicitOrder
examples_dirs = [
tvm_path.joinpath("docs", "get_started", "tutorials"),
tvm_path.joinpath("docs", "how_to", "tutorials"),
tvm_path.joinpath("docs", "deep_dive", "relax", "tutorials"),
tvm_path.joinpath("docs", "deep_dive", "tensor_ir", "tutorials"),
]
gallery_dirs = [
"get_started/tutorials/",
"how_to/tutorials/",
"deep_dive/relax/tutorials/",
"deep_dive/tensor_ir/tutorials/",
]
# Explicitly define the order within a subsection.
# The listed files are sorted according to the list.
# The unlisted files are sorted by filenames.
# The unlisted files always appear after listed files.
within_subsection_order = {}
class WithinSubsectionOrder:
def __init__(self, src_dir):
self.src_dir = src_dir.split("/")[-1]
def __call__(self, filename):
# If the order is provided, use the provided order
if (
self.src_dir in within_subsection_order
and filename in within_subsection_order[self.src_dir]
):
index = within_subsection_order[self.src_dir].index(filename)
assert index < 1e10
return f"\0{index:010d}"
# Otherwise, sort by filename
return filename
# When running the tutorials on GPUs we are dependent on the Python garbage collector
# collecting TVM packed function closures for any device memory to also be released. This
# is not a good setup for machines with lots of CPU ram but constrained GPU ram, so force
# a gc after each example.
def force_gc(gallery_conf, fname):
gc.collect()
filename_pattern_default = ".*"
sphinx_gallery_conf = {
"backreferences_dir": "gen_modules/backreferences",
"doc_module": ("tvm", "numpy"),
"reference_url": {
"tvm": None,
# "matplotlib": "https://matplotlib.org/",
# "numpy": "https://numpy.org/doc/stable",
},
"examples_dirs": examples_dirs,
"within_subsection_order": WithinSubsectionOrder,
"gallery_dirs": gallery_dirs,
"filename_pattern": os.environ.get("TVM_TUTORIAL_EXEC_PATTERN", filename_pattern_default),
"download_all_examples": False,
"min_reported_time": 60,
"expected_failing_examples": [],
"reset_modules": ("matplotlib", "seaborn", force_gc, install_request_hook),
"promote_jupyter_magic": True,
# Drop the "Gallery generated by Sphinx-Gallery" signature line on generated pages.
"show_signature": False,
}
autodoc_default_options = {
"member-order": "bysource",
}
# Maps the original namespace to list of potential modules
# that we can import alias from.
tvm_alias_check_map = {
"tvm.te": ["tvm.tirx"],
"tvm.tirx": ["tvm.ir", "tvm.runtime"],
}
def update_alias_docstring(name, obj, lines):
"""Update the docstring of alias functions.
This function checks if the obj is an alias of another documented object
in a different module.
If it is an alias, then it will append the alias information to the docstring.
Parameters
----------
name : str
The full name of the object in the doc.
obj : object
The original object.
lines : list
The docstring lines, need to be modified inplace.
"""
arr = name.rsplit(".", 1)
if len(arr) != 2:
return
target_mod, target_name = arr
if target_mod not in tvm_alias_check_map:
return
if not hasattr(obj, "__module__"):
return
obj_mod = obj.__module__
for amod in tvm_alias_check_map[target_mod]:
if not obj_mod.startswith(amod):
continue
if hasattr(sys.modules[amod], target_name):
obj_type = ":py:func" if callable(obj) else ":py:class"
lines.append(f".. rubric:: Alias of {obj_type}:`{amod}.{target_name}`")
tvm_class_name_rewrite_map = {
"tvm.tirx": ["Var", "Call"],
"tvm.relax": ["Var", "Call", "StringImm"],
"tvm.relax.frontend.nn": ["Module"],
}
# When documenting modules under these prefixes, prefer types from the mapped module
# to resolve ambiguous cross-references (e.g. Var exists in both tvm.tirx and tvm.relax).
tvm_module_type_preference = {
"tvm.s_tir": "tvm.tirx",
}
def distinguish_class_name(name: str, lines: list[str]):
"""Distinguish the docstring of type annotations.
In the whole TVM, there are many classes with the same name but in different modules,
e.g. ``tirx.Var``, ``relax.Var``. This function is used to distinguish them in the docstring,
by adding the module name as prefix.
To be specific, this function will check the current object name, and if it in the specific
module with specific name, it will add the module name as prefix to the class name to prevent
the confusion. Further, we only add the prefix to those standalone class name, but skip
the pattern of `xx.Var`, `Var.xx` and `xx.Var.xx`.
Parameters
----------
name : str
The full name of the object in the doc.
lines : list
The docstring lines, need to be modified inplace.
"""
remap = {}
for module_name in tvm_class_name_rewrite_map:
if name.startswith(module_name):
short_name = module_name[4:] if module_name.startswith("tvm.") else module_name
for class_name in tvm_class_name_rewrite_map[module_name]:
remap.update({class_name: f"{short_name}.{class_name}"})
for k, v in remap.items():
for i in range(len(lines)):
lines[i] = re.sub(rf"(?<!\.)\b{k}\b(?!\.)", v, lines[i])
def process_docstring(app, what, name, obj, options, lines):
"""Sphinx callback to process docstring"""
if callable(obj) or inspect.isclass(obj):
update_alias_docstring(name, obj, lines)
distinguish_class_name(name, lines)
def strip_ipython_magic(app, docname, source):
"""Prevents IPython magic commands from being rendered in HTML files.
TODO rework this function to remove IPython magic commands from include directives too.
"""
for i in range(len(source)):
source[i] = re.sub(r"%%.*\n\s*", "", source[i])
def _patch_python_domain_find_obj():
"""Patch PythonDomain.find_obj to resolve ambiguous cross-references.
Sphinx's ``warn-missing-reference`` event is only fired for unresolved
references. Ambiguous short names such as ``StringImm`` already have
multiple matches at ``PythonDomain.find_obj`` time, so the disambiguation
needs to happen here instead.
"""
from sphinx.domains.python import PythonDomain
if getattr(PythonDomain.find_obj, "_tvm_patched", False):
return
_original_find_obj = PythonDomain.find_obj
def _common_prefix_len(lhs: str, rhs: str) -> int:
count = 0
for lpart, rpart in zip(lhs.split("."), rhs.split(".")):
if lpart != rpart:
break
count += 1
return count
def _dedup_find_obj(self, env, modname, classname, name, objtype, searchmode=0):
matches = _original_find_obj(self, env, modname, classname, name, objtype, searchmode)
if len(matches) <= 1:
return matches
short_name = name.rsplit(".", 1)[-1]
# Prefer a single canonical (non-aliased) entry if Sphinx already found one.
canonical_matches = [match for match in matches if not match[1].aliased]
if len(canonical_matches) == 1:
return canonical_matches
# Use TVM's module context for the known short names we rewrite in docstrings.
if modname:
candidate_modules = sorted(
(
module_name
for module_name, class_names in tvm_class_name_rewrite_map.items()
if short_name in class_names and modname.startswith(module_name)
),
key=len,
reverse=True,
)
for module_name in candidate_modules:
target_name = f"{module_name}.{short_name}"
context_matches = [match for match in matches if match[0] == target_name]
if len(context_matches) == 1:
return context_matches
# Fall back to the unique match that best shares the current module prefix.
match_scores = {match[0]: _common_prefix_len(modname, match[0]) for match in matches}
best_score = max(match_scores.values())
if best_score > 1:
best_matches = [match for match in matches if match_scores[match[0]] == best_score]
if len(best_matches) == 1:
return best_matches
# Check module type preference for cross-module resolution
# (e.g. tvm.s_tir.analysis uses types from tvm.tirx).
for prefix, preferred_mod in tvm_module_type_preference.items():
if modname.startswith(prefix):
preferred = [m for m in matches if m[0].startswith(preferred_mod + ".")]
if len(preferred) >= 1:
return preferred[:1]
return matches
_dedup_find_obj._tvm_patched = True
PythonDomain.find_obj = _dedup_find_obj
_patch_python_domain_find_obj()
def setup(app):
app.connect("source-read", strip_ipython_magic)
app.connect("autodoc-process-docstring", process_docstring)
+249
View File
@@ -0,0 +1,249 @@
.. 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.
.. _ci_guide:
Using TVM's CI
==============
.. contents::
:local:
TVM uses a combination of Jenkins and GitHub Actions for continuous integration (CI).
- **Jenkins** runs Linux CI tests on
`branches <https://ci.tlcpack.ai/job/tvm/>`_ and
`pull requests <https://ci.tlcpack.ai/job/tvm/view/change-requests/>`_ through a
build configuration specified in `Jenkinsfile templates <https://github.com/apache/tvm/blob/main/ci/jenkins/templates/>`_.
Jenkins is the primary CI step that is codified to block merging.
- **GitHub Actions** runs `linting <https://github.com/apache/tvm/blob/main/.github/workflows/lint.yml>`_
(via pre-commit hooks) on pushes and pull requests, as well as minimal
Windows and macOS build-and-test jobs.
This page describes how contributors and committers can use TVM's CI to verify their code. You can
read more about the design of TVM CI in the `tlc-pack/ci <https://github.com/tlc-pack/ci>`_ repo.
For Contributors
----------------
A standard Jenkins CI run looks something like this viewed in `Jenkins' BlueOcean viewer <https://ci.tlcpack.ai/blue/organizations/jenkins/tvm/activity>`_.
CI runs usually take a couple hours to complete and pull requests (PRs) cannot be merged before CI
has successfully completed. To diagnose failing steps, click through to the failing
pipeline stage then to the failing step to see the output logs. For GitHub Actions jobs (lint,
Windows, macOS), check the "Actions" tab on the pull request or repository page.
.. image:: https://github.com/tlc-pack/web-data/raw/main/images/contribute/ci.png
:width: 800
:alt: The Jenkins UI for a CI run
Debugging Failures
^^^^^^^^^^^^^^^^^^
When CI fails for some reason, there are several methods to diagnose the issue.
Jenkins Logs
""""""""""""
.. |pytest| replace:: ``pytest``
.. _pytest: https://docs.pytest.org/en/stable/
The first place to look for a failure is in the CI logs, follow the red Xs on
the failing job to view the logs. Note:
* Jenkins does not display the full log by default, at the top of the log viewer
is a button "Show complete log" which will take you to a plaintext version of the log
* |pytest|_ failures are summarized at the bottom of the log but you will likely
need to scroll up to view the actual failure.
Reproduce Failures
""""""""""""""""""
Most TVM Python tests run under |pytest|_ and can be run as described in :ref:`pr-testing`.
Reporting Issues
^^^^^^^^^^^^^^^^
Issues with CI should be `reported on GitHub <https://github.com/apache/tvm/issues/new?assignees=&labels=&template=ci-problem.md&title=%5BCI+Problem%5D+>`_
with a link to the relevant jobs, commits, or PRs.
For Maintainers
---------------
This section discusses processes ran by TVM Maintainers.
Procedures for Keeping CI Green
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This section talks about common procedures used to keep CI passing.
Broken CI due to Simultaneous Merge
"""""""""""""""""""""""""""""""""""
Developers rely on the TVM CI to get signal on their PRs before merging. Occasionally, two
different PRs can pass CI individually but break ``main`` when both land. This in turn causes an
error to show up on an unrelated PR that is based on the broken commit(s). Broken commits can be
identified `through GitHub <https://github.com/apache/tvm/commits/main>`_ via the commit status icon
or via `Jenkins <https://ci.tlcpack.ai/blue/organizations/jenkins/tvm/activity?branch=main>`_.
In these situations it is ultimately the responsibility of the TVM Committer who merged the PR to
fix CI (others are encouraged to help). Typical responses to this situation are:
1. revert the offending commit
2. submit a forward fix to address the issue.
It is up to the committer and commit author which option to choose. A broken CI affects all TVM
developers and should be fixed as soon as possible, while a revert may be especially painful for the
author of the offending PR when that PR is large.
Dealing with Flakiness
^^^^^^^^^^^^^^^^^^^^^^
If you notice a failure on your PR that seems unrelated to your change, you should
search `recent GitHub issues related to flaky tests <https://github.com/apache/tvm/issues?q=is%3Aissue+%5BCI+Problem%5D+Flaky>`_ and
`file a new issue <https://github.com/apache/tvm/issues/new?assignees=&labels=&template=ci-problem.md&title=%5BCI+Problem%5D>`_
if you don't see any reports of the failure. If a certain test or class of tests affects
several PRs or commits on ``main`` with flaky failures, the test should be disabled via
`pytest's @xfail decorator <https://docs.pytest.org/en/stable/how-to/skipping.html#xfail-mark-test-functions-as-expected-to-fail>`_ with `strict=False <https://docs.pytest.org/en/stable/how-to/skipping.html#strict-parameter>`_ and the relevant issue linked in the
disabling PR.
.. code-block:: python
@pytest.mark.xfail(strict=False, reason="Flaky test: https://github.com/apache/tvm/issues/1234")
def test_something_flaky():
pass
Then submit a PR as usual
.. code-block:: bash
git add <test file>
git commit -m'[skip ci][ci] Disable flaky test: ``<test_name>``
See #<issue number>
'
gh pr create
Skipping CI
^^^^^^^^^^^
For reverts and trivial forward fixes, adding ``[skip ci]`` to the revert's
PR title will cause Jenkins CI to shortcut and only run lint. Committers should
take care that they only merge CI-skipped PRs to fix a failure on ``main`` and
not in cases where the submitter wants to shortcut CI to merge a change faster.
The PR title is checked when the build is first run (specifically during the Jenkins lint
step, so changes after that has run do not affect CI and will require the job to
be re-triggered by another ``git push``). Note that GitHub Actions lint always
runs independently via pre-commit hooks.
.. code-block:: bash
# Revert HEAD commit, make sure to insert '[skip ci]' at the beginning of
# the commit subject
git revert HEAD
git checkout -b my_fix
# After you have pushed your branch, create a PR as usual.
git push my_repo
# Example: Skip CI on a branch with an existing PR
# Adding this commit to an existing branch will cause a new CI run where
# Jenkins is skipped
git commit --allow-empty --message "[skip ci] Trigger skipped CI"
git push my_repo
Docker Images
^^^^^^^^^^^^^
Each CI job runs most of its work inside a Docker container, built from files
in the `docker/ <https://github.com/apache/tvm/tree/main/docker>`_ folder.
Updating a Docker Image Tag
"""""""""""""""""""""""""""
To update a tag, a new image needs to be built and uploaded to Docker Hub, then
the image tags in `docker-images.ini <https://github.com/apache/tvm/tree/main/ci/jenkins/docker-images.ini>`_
need to be updated to match the image tags on Docker Hub.
Docker images are built automatically nightly via the `tvm-docker <https://ci.tlcpack.ai/job/tvm-docker/>`_,
which uploads the built images to https://hub.docker.com/u/tlcpackstaging once
they have passed CI. Post-merge CI runs on ``main`` build Docker images ad-hoc
and upload them to the ``tlcpackstaging`` Docker Hub account as well. There is an
auto-promotion process for ``tlcpackstaging`` Docker images to be moved to the
``tlcpack`` account. This means that image tags from ``tlcpackstaging`` can be
used in CI and they will be automatically moved to ``tlcpack`` after a successful
post-merge CI run on ``main``. So the steps to update the image are:
1. Merge a PR that changes the Dockerfiles under ``docker/`` or scripts in ``docker/install``.
2. Do either of:
a. Wait for the post-merge CI build from the PR to complete and upload the newly built image to the `tlcpackstaging <https://hub.docker.com/u/tlcpackstaging>`_ Docker Hub.
b. Wait for the nightly Docker image build to complete and upload the newly built image to the `tlcpackstaging <https://hub.docker.com/u/tlcpackstaging>`_ Docker Hub.
3. Find the newly uploaded image tag on the `tlcpackstaging <https://hub.docker.com/u/tlcpackstaging>`_ Docker Hub, for example ``20221208-070144-22ff38dff`` and update the tag in ``ci/jenkins/docker-images.ini`` to use the tlcpackstaging tag but under the tlcpack account, e.g. ``tlcpack/ci-arm:20221208-070144-22ff38dff``. Send in a PR with these changes and wait for it to run through CI to ensure the new images are valid.
4. Merge the ``docker-images.ini`` update PR. Once post-merge CI finishes running on ``main`` the ``tlcpackstaging`` tag will be re-uploaded to ``tlcpack`` automatically.
Adding a New Docker Image
"""""""""""""""""""""""""
New docker images can be added to test TVM on a variety of platforms. Here are the steps for adding
a new CI image:
1. Define the ``docker/Dockerfile.ci_foo`` and associated scripts in ``docker/install``. Create a PR containing only these changes (no ``Jenkinsfile`` changes).
Example: https://github.com/apache/tvm/pull/12230/files
2. A committer verifies the image builds locally and then reviews/approves this PR.
3. A committer creates the ci-foo repos in https://hub.docker.com/u/tlcpack and https://hub.docker.com/u/tlcpackstaging.
4. Create a PR to create an ECR repo for the image in tlcpack/ci: https://github.com/tlc-pack/ci/pull/46/files
5. A committer creates and gets merged a PR to add the image to the ``Jenkinsfile``
Example: https://github.com/apache/tvm/pull/12369/files.
**NOTE**: The PR must be opened from a branch in apache/tvm, not from a branch in a forked repo.
6. A committer adds this image to the daily docker rebuild/validation run in tlcpack.
Example: https://github.com/tlc-pack/tlcpack/pull/131
``ci-docker-staging``
^^^^^^^^^^^^^^^^^^^^^
The `ci-docker-staging <https://github.com/apache/tvm/tree/ci-docker-staging>`_
branch is typically used to test updates to Docker images and ``Jenkinsfile`` changes. When
running a build for a normal PR from a forked repository, Jenkins uses the code
from the PR except for the ``Jenkinsfile`` itself, which comes from the base branch.
When branches are built, the ``Jenkinsfile`` in the branch is used, so a committer
with write access must push PRs to a branch in apache/tvm to properly test
``Jenkinsfile`` changes. If your PR makes changes to the ``Jenkinsfile``, make sure
to @ a `committer <https://github.com/apache/tvm/tree/main/CONTRIBUTORS.md>`_
and ask them to push your PR as a branch to test the changes.
CI Monitoring Rotation
^^^^^^^^^^^^^^^^^^^^^^
Some tests are also flaky and occasionally fail for reasons unrelated to the PR. The
`CI monitoring rotation <https://github.com/apache/tvm/wiki/CI-Monitoring-Runbook>`_ watches for these failures and
disables tests as necessary. It is the responsibility of those who wrote the test to ultimately fix
and re-enable the test.
+185
View File
@@ -0,0 +1,185 @@
.. 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.
.. _code_guide:
Code Guide and Tips
===================
.. contents::
:depth: 2
:local:
This is a document used to record tips in TVM codebase for reviewers and contributors.
Most of them are summarized through lessons during the contributing and process.
C++ Code Styles
---------------
- Use the Google C/C++ style.
- The public facing functions are documented in doxygen format.
- Favor concrete type declaration over ``auto`` as long as it is short.
- Favor passing by const reference (e.g. ``const Expr&``) over passing by value.
Except when the function consumes the value by copy constructor or move,
pass by value is better than pass by const reference in such cases.
- Favor ``const`` member function when possible.
We use ``clang-format`` to enforce the code style. Because different version
of clang-format might change by its version, it is recommended to use the same
version of the clang-format as the main one.
You can use pre-commit hooks to run formatting checks:
.. code:: bash
# Run clang-format on all files
pre-commit run clang-format --all-files
# Run all linters (Python + C++ + custom checks)
pre-commit run --all-files
clang-format is also not perfect, when necessary, you can use disble clang-format on certain code regions.
.. code :: c
// clang-format off
void Test() {
// clang-format will be disabled in this region.
}
// clang-format on
Because clang-format may not recognize macros, it is recommended to use macro like normal function styles.
.. code :: c
#define MACRO_IMPL { custom impl; }
#define MACRO_FUNC(x)
// not preferred, because clang-format might recognize it as types.
virtual void Func1() MACRO_IMPL
// preferred
virtual void Func2() MACRO_IMPL;
void Func3() {
// preferred
MACRO_FUNC(xyz);
}
Python Code Styles
------------------
- The functions and classes are documented in `numpydoc <https://numpydoc.readthedocs.io/en/latest/>`_ format.
- Check your code style using ``pre-commit run --all-files``
- Stick to language features in ``python 3.10``
- For functions with early returns, prefer ``if``/``elif``/``else``
chains for functions with parallel and short bodies to the
conditions, such as functions that apply a simple mapping to the
arguments. For more procedural functions, especially where the
final ``else`` block would be much longer than the ``if`` and
``elif`` blocks, prefer having the final ``else`` case unindented.
The pylint check ``no-else-return`` is disabled to allow for this
distinction. See further discussion `here
<https://github.com/apache/tvm/pull/11327>`_.
.. code:: python
# All cases have bodies with similar flow control. While this could
# be expressed as a sequence of if conditions, a reader would need to
# inspect the body of each condition to know that only one conditional
# body may be reached.
def sign(x):
if x > 0:
return "+"
elif x < 0:
return "-"
else:
return ""
# The initial special case is an early return for a special case,
# followed by a more general method. Using an else block for the
# condition would add unnecessary indentation for the remainder of the
# function.
def num_unique_subsets(values):
if len(values)==0:
return 1
# Longer, more general solution here
...
Writing Python Tests
--------------------
We use `pytest <https://docs.pytest.org/en/stable/>`_ for all Python testing. Regular tests live
under ``tests/python``, while environment-specific nightly tests live under ``tests/nightly/python``.
See :doc:`testing` for details on running tests, target parametrization,
and the target-specific marks used by CI.
If you want your test to run over a variety of targets, parametrize over ``target`` with ``@pytest.mark.parametrize``, tag GPU targets with ``@pytest.mark.gpu`` so the CI routes them to GPU nodes, and skip a target that is unavailable on the current machine with :py:func:`tvm.testing.device_enabled`. For example:
.. code:: python
@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_mytest(target):
if not tvm.testing.device_enabled(target):
pytest.skip(f"{target} not enabled")
dev = tvm.device(target)
...
will run ``test_mytest`` with ``target="llvm"`` and ``target="cuda"``, skipping any target whose device is not present. If you only want to test against a single target, drop the parametrization and hardcode the target. Mark GPU tests with ``@pytest.mark.gpu`` so the CI can select them, and skip when the required feature is unavailable with ``@pytest.mark.skipif``. For example, CUDA tests use:
.. code:: python
@pytest.mark.gpu
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
def test_mycudatest():
...
The ``tvm.testing.env`` module exposes a ``has_*()`` probe for each runtime and hardware feature (e.g. ``has_cuda()``, ``has_rocm()``, ``has_vulkan()``, ``has_llvm()``). To skip a test when an optional Python package is missing, use ``pytest.importorskip("package_name")``.
Network Resources
-----------------
In CI, downloading files from the Internet is a big source of flaky test failures (e.g. remote
server can go down or be slow), so try to avoid using the network at all during tests. In some cases
this isn't a reasonable proposition (e.g. the docs tutorials which need to download models).
New network downloads are rejected by the CI `request hook
<https://github.com/apache/tvm/blob/main/tests/python/request_hook.py>`_. Prefer
checked-in fixtures or generated data. If a download is unavoidable, arrange a stable
project-managed mirror with the maintainers and add an explicit ``URL_MAP`` entry.
Handle Integer Constant Expression
----------------------------------
We often need to handle constant integer expressions in TVM. Before we do so, the first question we want to ask is that is it really necessary to get a constant integer. If symbolic expression also works and let the logic flow, we should use symbolic expression as much as possible. So the generated code works for shapes that are not known ahead of time.
Note that in some cases we cannot know certain information, e.g. sign of symbolic variable, it is ok to make assumptions in certain cases. While adding precise support if the variable is constant.
If we do have to get constant integer expression, we should get the constant value using type ``int64_t`` instead of ``int``, to avoid potential integer overflow. We can always reconstruct an integer with the corresponding expression type via ``make_const``. The following code gives an example.
.. code:: c++
Expr CalculateExpr(Expr value) {
int64_t int_value = GetConstInt<int64_t>(value);
int_value = CalculateExprInInt64(int_value);
return make_const(value.type(), int_value);
}
+221
View File
@@ -0,0 +1,221 @@
.. 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.
.. _code_review_guide:
Code Reviews
============
.. contents::
:depth: 2
:local:
Open source code is maintained by a community with diverse backgrounds, interests, and goals.
Hence it is important to provide clear, documented and maintainable code and processes. Code reviews are a
shepherding process used to collectively spot potential problems, improve quality of the code, and educate both contributors
and reviewers about the code base and its assumptions. It is also one mechanism to ensure there are multiple people who can
maintain a related piece of code together. Contributors are encouraged to polish the code to a reviewable
state before requesting reviews. This is especially important for committer candidates, as committers are expected
to participate in not only writing code but also reviewing it.
This document is a living guideline for code review in open source. Please also take sometime to read
:ref:`community_guide` about the general development process.
Building Trust
--------------
First and foremost, we are building a community that based on trust, which takes time
and effort to both build and maintain. We expect our community members to work together in a
constructive way and work together with common sense. Although we all have different sets of backgrounds,
interests and goals we must work together to find solutions that work for the larger community.
Trust-based collaboration is also a key tenant of the Apache way and an important factor to consider in growing the community,
and promoting members to official roles.
Community Participation
-----------------------
Everyone is welcomed to comment on PRs. We encourage committers to wait for some period of time(e.g. three days)
before merging PR that contains a major architecture change. The goal is to give people time to speak up and
express interest in reviewing and participate.
Remembering that we are all coming from different backgrounds is important here. For example some community members
work in different time zones, only work on open source during work hours, or may be traveling or having other events
going on in their lives. An important part of working in a large project is ensuring there is collective understanding,
so no one person is a bottleneck. While it is important to allow time for participation in code review we also can not
block all changes on all reviewers. Remember that helping people land PRs is a great way to encourage broader
participation, especially for those who volunteer their time to contribute.
Part of this is trusting and communicating with fellow maintainers that if changes need to be applied in the future
that PR authors will later follow through on their promises. It is the responsibility of committers to listen to all
feedback whether from PMC members or new contributors and consider what actions need to be taken.
Read the code carefully
-----------------------
Sometimes we may quickly read through the code and only pick up on a selective aspects of the code. These type of comments
are usually helpful and should be welcomed in the community. However, they are only part of performing code review and
should be part of more comprehensive feedback. A good and careful code review is a large time investment and sometimes
can be longer than writing the actual contribution.
For example receiving only highly critical feedback on minor aspects of your PR rarely feels good, and it can be discouraging
if your time and effort was not reciprocated during review. Practicing empathy when acting both as a contributor and committer
is important and can help make you a more effective code reviewer and contributor.
We expect that all committers carefully read and understand the code before signing off. There is a lot of trust involved when
a committer hits the merge button. In the meantime, we acknowledge that sometimes problems slip through, in that case, the
merger is responsible for ensuring the correct follow up actions are taken.
Be Respectful
-------------
- To everyone who are making comments: making constructive comment will help new contributors to land their PRs
timely and help us welcome new members to the community.
- To authors: reviewers should spend significant time reading the code, and a careful review could be as time intensive
as writing the code from scratch. Respectfully address review comments and reciprocate the review by helping review
others changes in the future.
Most importantly focus on having a constructive conversation, and try to assume best intentions when interacting as a reviewer.
If there is something in the process not working, consider getting some face time with the other contributors and discussing
how to improve the process or communication.
Factors to Consider about Code Quality
--------------------------------------
High quality code is critical to the long term success of the project. There are many factors of code quality to consider
during a code review:
- F0: Overall architecture. This includes the definition of public modules, key data structures and public interfaces.
Good architectural choices are critical to the success of the project in the long run.
- F1: Architectural consistency. There are usually multiple ways to implement a new feature. We must ensure new
features are consistent with previous overall architectural choices and interact well with the existing code.
Every new feature increases the complexity of the project, and a consistent design ideally minimizes the increase
in complexity bought by a new feature, making it easier to maintain code in the long run.
- F2: Code robustness and test coverage. Ensure code runs correctly in all possible settings(platforms), ensure
test coverage of the new feature. Clear error messages for user facing errors.
- F3: User facing API documentation: documentation of public user facing APIs and key module interfaces are mandatory.
This includes the API, data structures that appears in the public interface (i.e., `include/tvm` and user facing python APIs).
We generally encourage well documented code and include some form of documentations for internal APIs that are used in
multiple places, see also F4.
- F4: Code readability. Readability involves multiple aspects: instructive and consistent function names, clear implementation
of the overall flow, descriptive comments for complex code logic and internal functions. Readable code is easier to maintain.
Architectural design and consistency are the most important factors since they are likely to introduce the most long term technical debt.
As a result, committers should most carefully consider these factors before merging the code.
Test coverage and API documentation are expected for code contributions.
Code readability is relatively a subjective matter compared to the other ones.
Different people have different thoughts on how to best write code. Reviewers should make constructive and actionable comments.
In the meantime, code review should not be used as a way to get others to write code exactly the way you would.
Conversely you should also consider that what you may easily understand, or find acceptable might not work for the larger
community or other members. Use your judgment on what is appropriate based on the content and the scope of the contribution
and where the contributor is coming from.
We follow common :ref:`code_guide` when writing code. Style guides help ensure that code is readable and maintainable by others,
long after the original author has moved on. Style guides are more than about code formatting — they also pertain
to the correct way to document code, variable naming, and other conventions that are not enforced by automatic formatters.
Consensus Building
------------------
Disagreements can happen during code reviews. We encourage building consensus among the people involved. We are working together
and building trust with each other in OSS. The nature of OSS means sometimes we make compromises on less significant issues to
make steady progress and welcome broader participation in the community. Compromise unfortunately means sometimes the world will
not be exactly as we would like, this true even for leaders of the community.
- Be civil and build consensus through constructive technical-based conversations.
- A committer who owns the area can serve as a shepherd to drive the discussion by taking all the conversations into consideration,
and suggest a resolution with to move forward.
- Because a lot of trust is involved on the committer(shepherd), they should read the PR carefully before sign off. Additionally,
the merger should also take the responsibility to followup in case there are problems caused by the merge.
Consistency
-----------
A final remark is that we are all human and its hard to always be perfectly consistent. If contributors feel that you didn't apply these guidelines
in a consistent way it is important to listen and hear folks out. We will constantly have to iterate on processes and guidelines as we evolve as a community.
Our goal is to strive to be consistent and objective but all of us are unfortunately human and imperfect and will need to adjust and learn.
Additional Recommendations
--------------------------
Deliberate on API and Data Structures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A minimum and stable API is critical to the projects life. A good API makes a huge difference. Always think very carefully about all the aspects including naming, argument definitions and behavior.
When possible, pay more attention still to the proposed API design during code reviews.
Remember, it is easier to improve code implementation, but it is extremely hard to change an API once accepted.
We should treat data structures that are shared across modules(e.g. AST) in the same way.
If/when uncertain, start a conversation with more developers before committing.
Here are some useful principles for designing APIs:
- Be consistent with existing well-known packages APIs if the features overlap.
For example, tensor operation APIs should always be consistent with the numpy API.
- Be consistent with existing APIs in the same project.
For example, we should use the same argument ordering across all the optimization passes,
so there is no "surprise" when using them.
- Think about whether the API will change in the future.
For example, we will have more options like loop_unrolling and device placement policy
as we add more optimizations in build. We can package optimization knobs into a build
configuration object. In this way, the build API is stable over time, even though it may be enriched.
- Write documentation. Documentation is mandatory for APIs and sometimes writing documents helps
us to think further about the design as well as whether we need to add further clarifications.
- Minimum. Think about how many lines of code a user has to write to use the API.
Remove layers of abstraction when possible.
Minimize Dependencies
~~~~~~~~~~~~~~~~~~~~~
Always be cautious in introducing dependencies. While it is important to reuse code and avoid reinventing the wheel,
dependencies can increase burden of users in deployment. A good design principle is that a feature or function
should only have a dependency if/when a user actually use it.
Concise Implementation
~~~~~~~~~~~~~~~~~~~~~~
Some basic principles applied here: favor vectorized array code over loops, use existing APIs that solve the problem.
Document Lessons in Code Reviews
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When you find there are some common or recurring lessons that can be summarized,
add it to the :ref:`code_guide`.
It is always good to refer to the guideline document when requesting changes,
so the lessons can be shared to all the community.
Learn from other Code Reviews
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There can be multiple reviewers reviewing the same changes. Many times other reviewers
may spot things you did not find. Try to learn from other code reviews, when possible, document these lessons.
Approve and Request Changes Explicitly
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The contributor and code owner can request code reviews from multiple reviewers.
Remember to approve changes when your comments are addressed in a code review.
To do so -- please click on changes tab in the pull request, then select approve,
or comment on the code and click request changes.
Code owner can decide if the code can be merged in case by case if some of the reviewers
did not respond in time(e.g. a week) and existing reviews are sufficient.
Reviewers
~~~~~~~~~
Reviewers should strive to leave timely feedback on pull requests for which their
review was requested. Reviewing code is an important part of the project's health
and should be considered a regular responsibility for contributors. Automated
tooling helps out in this regard, as PRs with no activity for a set amount of
time will get a bot comment pinging the relevant parties.
+121
View File
@@ -0,0 +1,121 @@
.. 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.
.. _committer_guide:
Committer Guide
===============
.. contents::
:depth: 2
:local:
This is an evolving document to provide some helpful tips for committers.
Most of them are lessons learned during development.
We welcome every committer to contribute to this document.
See the :ref:`community_guide` for an overview of
the committership and the general development process.
Community First
---------------
The collective effort of the community moves the project forward and
makes the project awesome for everyone.
When we make a decision, it is always helpful to keep the community in mind.
Here are some example questions that we can ask:
- How can I encourage new contributors to get more involved in the project?
- Can I help to save my fellow committers' time?
- Have I enabled the rest of the community to participate the
design proposals?
Public Archive Principle
------------------------
While private channels such as face to face discussion are useful for development,
they also create barriers for the broader community's participation.
The Apache way of development requires all decisions
to be made in public channels, which are archived and accessible to everyone.
As a result, any contributor can keep up with the development by watching the
archives and join the development anytime.
While this principle applies to every contributor,
it is especially important for committers.
Here are some example applications of this principle:
- When getting a project-related question from a personal channel,
encourage the person to open a public thread in the discuss forum,
so others in the community can benefit from the answer.
- After an in-person discussion, send a summary to public channels
(as an RFC or a discuss thread).
Independent Project Management
------------------------------
Everyone is presumed to be wearing their Apache committer hat when participating in the project.
That is, committers should act - in the context of the project activities - in the best interests of the project.
Separating your hat between committer and any other roles you may have is important in all aspects.
In the context of project participation, it can be helpful to state which hat you are wearing in cases where that
can cause confusion, especially in cases where you are not wearing committer hat. Two examples:
- "Wearing [foo] hat: [message when serving as foo's role and not as committer]".
- "Wearing Apache TVM hat: [messages when serving as committer]".
Shepherd a Pull Request
-----------------------
Here are some tips to shepherd a pull request.
You can also take a look at the :ref:`code_review_guide`.
- Assign the PR to yourself, so that other committers
know that the PR has already been tended to.
- Make use of the status label to indicate the current status.
- Check if an RFC needs to be sent.
- If the contributor has not requested a reviewer, kindly
ask the contributor to do so.
If the PR comes from a new contributor,
help the contributor to request reviewers
and ask the contributor to do so next time.
- Moderate the reviews, ask reviewers to approve explicitly.
- Mark the PR as accepted and acknowledge the contributor/reviewers.
- Merge the PR :)
Time Management
---------------
There are many things that a committer can do, such as
moderating discussions, pull request reviews and
code contributions.
Working on an open source project can be rewarding,
but also be a bit overwhelming sometimes.
A little bit of time management might be helpful to alleviate the problem.
For example, some committers have a "community day" in a week
when they actively manage outstanding PRs,
but watch the community less frequently in the rest of the time.
Remember that your merit will never go away, so please
take your time and pace when contributing to the project :)
Broad Collaboration
-------------------
Sometimes, we tend to only interact with people we know.
However, broad collaborations are necessary to the success of the project.
Try to keep that in mind, shepherd PRs for, and request code reviews from
community members who you do not interact physically.
+64
View File
@@ -0,0 +1,64 @@
.. 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.
.. _community_guide:
TVM Community Guidelines
========================
.. contents::
:depth: 2
:local:
TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use, contribute to, and influence the direction of the project. See `CONTRIBUTORS.md <https://github.com/apache/tvm/blob/main/CONTRIBUTORS.md>`_ for the current list of contributors.
General Development Process
---------------------------
Everyone in the community is welcomed to send patches, documents, and propose new directions to the project. The key guideline here is to enable everyone in the community to get involved and participate the decision and development. When major changes are proposed, an RFC should be sent to allow discussion by the community. We encourage public discussion, archivable channels such as issues, discuss forum and mailing-list, so that everyone in the community can participate and review the process later.
Code reviews are one of the key ways to ensure the quality of the code. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project. A pull request needs to be reviewed before it gets merged. A committer who has the expertise of the corresponding area would moderate the pull request and the merge the code when it is ready. The corresponding committer could request multiple reviewers who are familiar with the area of the code. We encourage contributors to request code reviews themselves and help review each other's code -- remember everyone is volunteering their time to the community, high-quality code review itself costs as much as the actual code contribution, you could get your code quickly reviewed if you do others the same favor.
The community should strive to reach a consensus on technical decisions through discussion. We expect committers and PMCs to moderate technical discussions in a diplomatic way, and provide suggestions with clear technical reasoning when necessary.
Strategy Decision Process
-------------------------
It takes lazy 2/3 majority (at least 3 votes and twice as many +1 votes as -1 votes) of binding decisions to make the following
strategic decisions in the TVM community:
- Adoption of a guidance-level community strategy to enable new directions or overall project evolution.
- Establishment of a new module in the project.
- Adoption of a new codebase: When the codebase for an existing, released product is to be replaced with an alternative codebase.
If such a vote fails to gain approval, the existing code base will continue. This also covers the creation of new sub-projects within the project.
All these decisions are made after community conversations that get captured as part of the summary.
Committers
----------
Committers are individuals who are granted the write access to the project. A committer is usually responsible for a certain area or several areas of the code where they oversee the code review process. The area of contribution can take all forms, including code contributions and code reviews, documents, education, and outreach. Committers are essential for a high quality and healthy project. The community actively look for new committers from contributors. Here is a list of useful traits that help the community to recognize potential committers:
- Sustained contribution to the project, demonstrated by discussion over RFCs, code reviews and proposals of new features, and other development activities. Being familiar with, and being able to take ownership on one or several areas of the project.
- Quality of contributions: High-quality, readable code contributions indicated by pull requests that can be merged without a substantial code review. History of creating clean, maintainable code and including good test cases. Informative code reviews to help other contributors that adhere to a good standard.
- Community involvement: active participation in the discussion forum, promote the projects via tutorials, talks and outreach. We encourage committers to collaborate broadly, e.g. do code reviews and discuss designs with community members that they do not interact physically.
The `Project Management Committee (PMC) <https://projects.apache.org/committee.html?tvm>`_ consists group of active committers that moderate the discussion, manage the project release, and proposes new committer/PMC members. Potential candidates are usually proposed via an internal discussion among PMCs, followed by a consensus approval, (i.e. at least 3 +1 votes, and no vetoes). Any veto must be accompanied by reasoning. PMCs should serve the community by upholding the community practices and guidelines TVM a better community for everyone. PMCs should strive to only nominate new candidates outside of their own organization.
Reviewers
---------
Reviewers are individuals who actively contributed to the project and are willing to participate in the code review of new contributions. We identify reviewers from active contributors. The committers should explicitly solicit reviews from reviewers. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project. A pull request to the project has to be reviewed by at least one reviewer in order to be merged.
+257
View File
@@ -0,0 +1,257 @@
.. 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.
.. _doc_guide:
Documentation
=============
.. contents::
:depth: 2
:local:
TVM documentation loosely follows the `formal documentation style described by
Divio <https://documentation.divio.com>`_. This system has been chosen because
it is a "simple, comprehensive and nearly universally-applicable scheme. It is
proven in practice across a wide variety of fields and applications."
This document describes the organization of TVM documentation, and how to write
new documentation. See `docs/README.md <https://github.com/apache/tvm/tree/main/docs#build-locally>`_
for instructions on building the docs.
The Four Document Types
***********************
Introductory Tutorials
----------------------
These are step by step guides to introduce new users to a project. An
introductory tutorial is designed to get a user engaged with the software
without necessarily explaining why the software works the way it does. Those
explanations can be saved for other document types. An introductory tutorial
focuses on a successful first experience. These are the most important docs to
turning newcomers into new users and developers. A fully end-to-end
tutorial — from installing TVM and supporting ML software, to creating and
training a model, to compiling to different architectures — will give a new
user the opportunity to use TVM in the most efficient way possible. A tutorial
teaches a beginner something they need to know. This is in contrast with a
how-to, which is meant to be an answer to a question that a user with some
experience would ask.
Tutorials need to be repeatable and reliable, because the lack of success means
a user will look for other solutions.
How-to Guides
-------------
These are step by step guides on how to solve particular problems. The user can
ask meaningful questions, and the documents provide answers. An examples of
this type of document might be, "how do I compile an optimized model for ARM
architecture?" or "how do I compile and optimize a PyTorch model?" These
documents should be open enough that a user could see how to apply it to a new
use case. Practical usability is more important than completeness. The title
should tell the user what problem the how-to is solving.
How are tutorials different from how-tos? A tutorial is oriented towards the
new developer, and focuses on successfully introducing them to the software and
community. A how-to, in contrast, focuses on accomplishing a specific task
within the context of basic understanding. A tutorial helps to on-board and
assumes no prior knowledge. A how-to assumes minimum knowledge, and is meant to
guide someone to accomplish a specific task.
Reference
---------
Reference documentation describes how the software is configured and operated.
APIs, key functions, commands, and interfaces are all candidates for reference
documentation. These are the technical manuals that let users build their own
interfaces and programs. They are information oriented, focused on lists and
descriptions. You can assume that the audience has a grasp on how the software
works and is looking for specific answers to specific questions. Ideally, the
reference documentation should have the same structure as the code base and be
generated automatically as much as possible.
Architecture Guides
-------------------
Architecture Guides are explanations are background material on a topic. These
documents help to illuminate and understand the application environment. Why
are things the way they are? What were the design decisions, what alternatives
were considered, what are the RFCs describing the existing system? This
includes academic papers and links to publications relevant to the software.
Within these documents you can explore contradictory and conflicting position,
and help the reader make sense of how and why the software was built the way it
is. It's not the place for how-tos and descriptions on how to accomplish tasks.
They instead focus on higher level concepts that help with the understanding of
the project. Generally these are written by the architects and developers of
the project, but can useful to help both users and developers to have a deeper
understanding of why the software works the way it does, and how to contribute
to it in ways that are consistent with the underlying design principles.
Special considerations for TVM
------------------------------
The TVM community has some special considerations that require deviation from
the simple docs style outlined by Divio. The first consideration is that there
is frequently overlap between the user and developer communities. Many projects
document the developer and user experience with separate systems, but it is
appropriate to consider both in this system, with differentiations where
appropriate. As a result the tutorials and how-tos will be divided between
"User Guides" that focus on the user experience, and "Developer Guides" that
focus on the developer experience.
The next consideration is that there are special topics within the TVM
community that benefit from additional attention. Special "Topic Guides" can be
created to index existing material, and provide context on how to navigate that
material most effectively.
To facilitate newcomers, a special "Getting Started" section with installation
instructions, a overview of why to use TVM, and other first-experience
documents will be produced.
Technical Details
*****************
We use the `Sphinx <https://www.sphinx-doc.org>`_ for the main documentation.
Sphinx supports both reStructuredText and markdown. When possible, we
encourage reStructuredText as it has richer features. Note that the
Python doc-string and tutorials allow you to embed reStructuredText syntax.
See
`docs/README.md <https://github.com/apache/tvm/tree/main/docs#build-locally>`_
for instructions on building the docs.
Python Reference Documentation
------------------------------
We use the `numpydoc <https://numpydoc.readthedocs.io/en/latest/>`_ format to
document the function and classes. The following snippet gives an example
docstring. We always document all the public functions, and when necessary,
provide a usage example of the features we support (as shown below).
.. code:: python
def myfunction(arg1, arg2, arg3=3):
"""Briefly describe my function.
Parameters
----------
arg1 : Type1
Description of arg1
arg2 : Type2
Description of arg2
arg3 : Type3, optional
Description of arg3
Returns
-------
rv1 : RType1
Description of return type one
Examples
--------
.. code:: python
# Example usage of myfunction
x = myfunction(1, 2)
"""
return rv1
Be careful to leave blank lines between sections of your documents. In the
above case, there has to be a blank line before ``Parameters``, ``Returns`` and
``Examples`` in order for the doc to be built correctly. To add a new function to
the docs, we need to add the `sphinx.autodoc
<https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_ rules to
`docs/reference/api/python <https://github.com/apache/tvm/tree/main/docs/reference/api/python>`_.
You can refer to the existing files under this folder on how to add the
functions.
C++ Reference Documentation
---------------------------
We use the doxygen format to document c++ functions. The following snippet
shows an example of c++ docstring.
.. code:: c++
/*!
* \brief Description of my function
* \param arg1 Description of arg1
* \param arg2 Description of arg2
* \returns describe return value
*/
int myfunction(int arg1, int arg2) {
// When necessary, also add comment to clarify internal logics
}
Besides documenting function usages, we also highly recommend contributors to
add comments about code logics to improve readability.
Sphinx Gallery How-Tos
----------------------
We use `sphinx-gallery <https://sphinx-gallery.github.io/>`_ to build many
Python how-tos. You can find the source code under `docs/how_to/tutorials
<https://github.com/apache/tvm/tree/main/docs/how_to/tutorials>`_.
One thing that worth noting is that the comment blocks are written in
reStructuredText instead of markdown so be aware of the syntax.
The how-to code will run on our build server to generate the document page. So
we may have a restriction like not being able to access a remote Raspberry Pi,
in such case add a flag variable to the tutorial (e.g. ``use_rasp``) and allow
users to easily switch to the real device by changing one flag. Then use the
existing environment to demonstrate the usage.
If you add a new categorization of how-to, you will need to add references to
`conf.py <https://github.com/apache/tvm/tree/main/docs/conf.py>`_ and the
`top-level docs index <https://github.com/apache/tvm/tree/main/docs/index.rst>`_
(how-to entries are registered there directly, not in a separate how-to index).
Refer to Another Location in the Document
-----------------------------------------
Please use sphinx's ``:ref:`` markup to refer to another location in the same doc.
.. code-block:: rst
.. _document-my-section-tag
My Section
----------
You can use :ref:`document-my-section-tag` to refer to My Section.
Documents with Images / Figures
-------------------------------
reStructuredText's `figure <https://docutils.sourceforge.io/docs/ref/rst/directives.html#figure>`_
and `image <https://docutils.sourceforge.io/docs/ref/rst/directives.html#image>`_
elements allow a document to include an image URL.
Image files created for TVM documentation should reside in the `<https://github.com/tlc-pack/web-data>`_
repository, while the `.rst` files *using* those images should reside in the main TVM repostitory
(`<https://github.com/apache/tvm>`_).
This will require two GitHub Pull Requests, one for the image files and another for the `.rst` files.
Discussion between the contributor and reviewers may be necessary to coordinate the review process.
*IMPORTANT NOTE:* When using two Pull Requests as described above, please merge the
Pull Request in `<https://github.com/tlc-pack/web-data>`_ *before* merging
the Pull Request in `<https://github.com/apache/tvm>`_.
This helps ensure that all URL links in TVM's online documentation are valid.
+97
View File
@@ -0,0 +1,97 @@
.. 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.
.. _error-handling-guide:
Error Handling Guide
====================
.. contents::
:depth: 2
:local:
TVM contains structured error classes to indicate specific types of error.
Please raise a specific error type when possible, so that users can
write code to handle a specific error category if necessary.
You can directly raise the specific error object in python.
In other languages like c++, you simply add ``<ErrorType>:`` prefix to
the error message(see below).
.. note::
Please refer to :py:mod:`tvm.error` for the list of errors.
Raise a Specific Error in C++
-----------------------------
You can add ``<ErrorType>:`` prefix to your error message to
raise an error of the corresponding type.
Note that you do not have to add a new type
:py:class:`tvm.error.InternalError` will be raised by default when
there is no error type prefix in the message.
This mechanism works for both ``LOG(FATAL)`` and ``TVM_FFI_ICHECK`` macros.
The following code gives an example on how to do so.
.. code:: c
// src/api_test.cc
void ErrorTest(int x, int y) {
TVM_FFI_ICHECK_EQ(x, y) << "ValueError: expect x and y to be equal."
if (x == 1) {
LOG(FATAL) << "InternalError: cannot reach here";
}
}
When a C++ function registered via the FFI raises an error with a typed prefix,
the TVM FFI system will automatically map it to the corresponding Python exception
class. For example, a ``ValueError:`` prefix in the error message will raise a Python
``ValueError``, and an ``InternalError:`` prefix will raise ``tvm.error.InternalError``.
TVM's FFI system combines both the Python and C++ stacktraces into a single message,
and generates the corresponding error class automatically.
How to choose an Error Type
---------------------------
You can go through the error types are listed below, try to use common
sense and also refer to the choices in the existing code.
We try to keep a reasonable amount of error types.
If you feel there is a need to add a new error type, do the following steps:
- Send a RFC proposal with a description and usage examples in the current codebase.
- Add the new error type to :py:mod:`tvm.error` with clear documents.
- Update the list in this file to include the new error type.
- Change the code to use the new error type.
We also recommend to use less abstraction when creating the short error messages.
The code is more readable in this way, and also opens path to craft specific
error messages when necessary.
.. code:: python
def preferred():
# Very clear about what is being raised and what is the error message.
raise OpNotImplemented("Operator relu is not implemented in the ONNX frontend")
def _op_not_implemented(op_name):
return OpNotImplemented("Operator {} is not implemented.").format(op_name)
def not_preferred():
# Introduces another level of indirection.
raise _op_not_implemented("relu")
If we need to introduce a wrapper function that constructs multi-line error messages,
please put wrapper in the same file so other developers can look up the implementation easily.
+143
View File
@@ -0,0 +1,143 @@
.. 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.
.. _git-howto:
Git Usage Tips
==============
.. contents::
:depth: 2
:local:
Here are some tips for git workflow.
How to resolve a conflict with ``main``
---------------------------------------
- First rebase to most recent main
.. code:: bash
# The first two steps can be skipped after you do it once.
git remote add upstream [url to tvm repo]
git fetch upstream
git rebase upstream/main
- The git may show some conflicts it cannot merge, say ``conflicted.py``.
- Manually modify the file to resolve the conflict.
- After you resolved the conflict, mark it as resolved by
.. code:: bash
git add conflicted.py
- Then you can continue rebase by
.. code:: bash
git rebase --continue
- Finally push to your fork, you may need to force push here.
.. code:: bash
git push --force
How to combine multiple commits into one
----------------------------------------
Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
to create a PR with set of meaningful commits. You can do it by following steps.
- Before doing so, configure the default editor of git if you haven't done so before.
.. code:: bash
git config core.editor the-editor-you-like
- Assume we want to merge last 3 commits, type the following commands
.. code:: bash
git rebase -i HEAD~3
- It will pop up an text editor. Set the first commit as ``pick``, and change later ones to ``squash``.
- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
- Push the changes to your fork, you need to force push.
.. code:: bash
git push --force
Reset to the most recent main branch
------------------------------------
You can always use git reset to reset your version to the most recent main.
Note that **all your local changes will get lost**.
So only do it when you do not have local changes or when your pull request just get merged.
.. code:: bash
git fetch origin main
git reset --hard FETCH_HEAD
Recover a Previous Commit after Reset
-------------------------------------
Sometimes we could mistakenly reset a branch to a wrong commit.
When that happens, you can use the following command to show the list
of recent commits
.. code:: bash
git reflog
Once you get the right hashtag, you can use git reset again to change
the head to the right commit.
Apply only k-Latest Commits on to the main
------------------------------------------
Sometimes it is useful to only apply your k-latest changes on top of the main.
This usually happens when you have other m-commits that are already merged
before these k-commits. Directly rebase against the main might cause merge conflicts
on these first m-commits (which can be safely discarded).
You can instead use the following command
.. code:: bash
# k is the concrete number
# Put HEAD~2 for the last 1 commit.
git rebase --onto upstream/main HEAD~k
You can then force push to the main. Note that the above command will discard
all the commits before tha last k ones.
What is the consequence of force push
-------------------------------------
The previous two tips requires force push, this is because we altered the path of the commits.
It is fine to force push to your own fork, as long as the commits changed are only yours.
+53
View File
@@ -0,0 +1,53 @@
.. 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.
Contributor Guide
=================
TVM has been developed by community members.
Everyone is welcomed to contribute.
We value all forms of contributions, including, but not limited to:
- Code reviewing of the existing patches.
- Documentation and usage examples
- Community participation in forums and issues.
- Code readability and developer guide
- We welcome contributions that add code comments
to improve readability
- We also welcome contributions to docs to explain the
design choices of the internal.
- Test cases to make the codebase more robust
- Tutorials, blog posts, talks that promote the project.
Here are guidelines for contributing to various aspect of the project:
.. toctree::
:maxdepth: 2
community
pull_request
code_review
committer_guide
document
code_guide
testing
git_howto
ci
release_process
error_handling
+267
View File
@@ -0,0 +1,267 @@
.. 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.
Submit a Pull Request
=====================
.. contents::
:depth: 2
:local:
Guidelines
----------
- We recommend authors send well scoped PRs that are easy to review and revert in case there is a problem. As such, authors should avoid merging multiple unrelated changes into a single PR
- Before you submit a PR, please rebase your code on the most recent version of ``main``, you can do it by
running
.. code:: bash
git remote add upstream [url to tvm repo]
git fetch upstream
git rebase upstream/main
- Make sure code passes lint checks
.. code:: bash
# Run all lint checks via pre-commit hooks
pre-commit run --all-files
# Run specific linters individually
pre-commit run ruff-check --all-files # Python lint
pre-commit run ruff-format --all-files # Python format
pre-commit run clang-format --all-files # C++ format
- Add test-cases to cover the new features or bugfix the patch introduces.
- Document the code you wrote, see more at :ref:`doc_guide`
- `Create a pull request <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request>`_ and fix the problems reported by CI checks.
- Request code reviews from other contributors and improve your patch according
to their reviews by ``@``-ing them in your pull request. Use relevant topic
tags in PR titles to make the affected area clear (e.g. ``[microTVM] Add a
cool change`` and not ``a cool change for microTVM``).
Please see the Commit Message Guideline below on the guidelines about the tags
in a PR/commit title and how to write good PR/commit messages.
- To get your code reviewed quickly, we encourage you to help review others' code so they can do the favor in return.
- Code review is a shepherding process that helps to improve contributor's code quality.
We should treat it proactively, to improve the code as much as possible before the review.
We highly value patches that can get in without extensive reviews.
- The detailed guidelines and summarizes useful lessons.
- The PR can be merged after the reviewers approve the pull request.
Commit Message Guideline
------------------------
Apache TVM uses the GitHub (GH) platform for patch submission and code review
via Pull Requests (PRs). The final commit (title and body) that is merged into
the Apache TVM main tree is composed of the PR's title and body and must be kept
updated and reflecting the new changes in the code as per the reviews and
discussions.
Although these guidelines apply essentially to the PRs title and body messages,
because GH auto-generates the PRs title and body from the commits on a given
branch, its recommended to follow these guidelines right from the beginning,
when preparing commits in general to be submitted to the Apache TVM project.
This will ease the creation of a new PR, avoiding rework, and also will help the
review.
The rules below will help to achieve uniformity that has several benefits, both
for review and for the code base maintenance as a whole, helping you to write
commit messages with a good quality suitable for the Apache TVM project,
allowing fast log searches, bisecting, and so on.
*PR/commit title*:
- Guarantee a title exists (enforced);
- Dont use GitHub usernames in the title, like @username (enforced);
- A tag must be present as a hint about what component(s) of the code
the PRs / commits “touch” (enforced). For example [BugFix], [CI], [microTVM],
and [TVMC]. Tags go between square brackets and appear first in the title. If
more than one tag exist, multiple brackets should be used, like [BugFix][CI].
The case recommended for tags, in geral, is the upper camel case. For example,
prefer the forms [Fix], [BugFix], and [Docker] instead of [fix], [bug_fix],
and [docker]. Acronyms should be kept as such so, for example, use [CI] and
[TVMC] instead of [ci] and [tvmc]. Tags help reviewers to identify the PRs
they can/want to review and also help the release folks when generating the
release notes;
- Use an imperative mood. Avoid titles like “Added operator X” and “Updated
image Y in the CI”, instead use the forms “Add feature X” and “Update image Y
in the CI” instead;
- Observe proper use of caps at the beginning (uppercase for the first letter)
and for acronyms, like, for instance, TVM, FVP, OpenCL. Hence instead of
“fix tvm use of opencl library”, write it as “Fix TVM use of OpenCL library”;
- Do not put a period at the end of the title.
*PR/commit body*:
- Guarantee a body exists (enforced);
- Dont use GitHub usernames in body text, like @username (enforced);
- Avoid “bullet” commit message bodies: “bullet” commit message bodies are not
bad per se, but “bullet” commit messages without any description or
explanation is likely as bad as commits without any description, rationale,
or explanation in the body.
For minor deviations from these guidelines, the community will normally favor
reminding the contributor of this policy over reverting or blocking a commit /
PR.
Commits and PRs without a title and/or a body are not considered minor
deviations from these guidelines and hence must be avoided.
Most importantly, the contents of the commit message, especially the body,
should be written to convey the intention of the change, so it should avoid
being vague. For example, commits with a title like “Fix”, “Cleanup”, and
“Fix flaky test” and without any body text should be avoided. Also, for the
review, it will leave the reviewer wondering about what exactly was fixed or
changed and why the change is necessary, slowing the review.
Below is an example that can be used as a model:
::
[microTVM] Zephyr: Remove zephyr_board option from build, flash, and open_transport methods
Currently its necessary to pass the board type via zephyr_board option to
the Project API build, flash, and open_transport methods.
However, since the board type is already configured when the project is
created (i.e. when the generate_project method is called), its possible to
avoid this redundancy by obtaining the board type from the project
configuration files.
This commit adds code to obtain the board type from the project CMake files,
removing this option from build, flash, and open_transport methods, so its
only necessary to specify the zephyr_board option when calling
generate_project.
This commit also moves the verbose and west_cmd options from build
method to generate_project, reducing further the number of required options
when building a project, since the build method is usually called more often
than the generate_project.
After a new PR is created and the review starts its common that reviewers will
request changes. Usually the author will address the reviewers comments and
push additional commits on top of the initial ones. For these additional commits
there is no recommendation regarding the commit messages. However if the
additional commits render the PR title and/or body outdated then it's the
author's responsibility to keep the PR title and body in sync with new changes
in the code and updated the PR title and body accordingly (remember that the PR
title and body will be used to compose the final commit message that will land
in the main tree).
Committers will seek to fix any issues with the commit message prior to
committing but they retain the right to inform the author of the rules and
encourage them to follow them in future. Also, they retain the right to ask to
the author to update the PR title and/or body when they are not correctly
updated or fixed.
CI Environment
--------------
We use Docker images to create stable CI environments that can be deployed to multiple machines.
Follow the steps in `this issue template <https://github.com/apache/tvm/issues/new?assignees=&labels=&template=ci-image.md&title=%5BCI+Image%5D+>`_
to update a CI Docker image.
.. _pr-testing:
Testing
-------
Even though we have hooks to run unit tests automatically for each pull request, it's always recommended to run unit tests
locally beforehand to reduce reviewers' burden and speedup review process.
Docker (recommended)
^^^^^^^^^^^^^^^^^^^^
``tests/scripts/ci.py`` replicates the CI environment locally and provides a user-friendly interface.
The same Docker images and scripts used in CI are used directly to run tests. It also deposits builds
in different folders so you can maintain multiple test environments without rebuilding from scratch
each time (e.g. you can test a change in CPU and GPU while retaining incremental rebuilds).
.. code:: bash
# see all available platforms
python tests/scripts/ci.py --help
python tests/scripts/ci.py cpu --help
# run the CPU build in the ci_cpu docker container (build will be left in
# the build-cpu/ folder)
# note: the CPU and GPU Docker images are quite large and may take some
# time to download on their first use
python tests/scripts/ci.py cpu
# run the CPU build in the ci_cpu docker container and then run unittests
python tests/scripts/ci.py cpu --unittest
# quickly iterate by running a specific test and skipping the rebuild each time
python tests/scripts/ci.py cpu --skip-build --tests tests/python/s_tir/schedule/test_tir_schedule_rolling_buffer.py::test_upscale
# run the CPU build and drop into a shell in the container
python tests/scripts/ci.py cpu --interactive
We regularly update our docker images and, over time, stale images may unnecessarily consume disk
space. You can remove stale images that aren't used in the presently checked-out branch plus any
other worktrees using the following command:
.. code:: bash
docker/clear-stale-images.sh
Consult the ``--help`` for more options.
C++ (local)
^^^^^^^^^^^
Running the C++ tests requires installation of gtest, following the instructions in
:ref:`install-from-source-cpp-tests`
.. code:: bash
# assume you are in tvm source root
TVM_ROOT=`pwd`
./tests/scripts/task_cpp_unittest.sh
Python (local)
^^^^^^^^^^^^^^
Necessary dependencies:
.. code:: bash
pip install --user pytest pytest-xdist Cython
If you want to run all tests:
.. code:: bash
# build tvm (see install-from-source for CMake build instructions)
cd build && cmake .. && cmake --build . --parallel $(nproc) && cd ..
python -m pytest -vvs -n auto tests/python
If you want to run a single test:
.. code:: bash
# let python know where to find tvm related libraries
export PYTHONPATH=python
rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc
python -m pytest -v tests/python/tirx-transform/test_tir_transform_storage_rewrite.py
# Additionally if you want to run a single test, for example test_add_one_2d inside a file.
python -m pytest -v -k "test_add_one_2d" tests/python/relax/test_frontend_tflite.py
+314
View File
@@ -0,0 +1,314 @@
.. 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.
.. _release_process:
Release Process
===============
.. contents::
:depth: 2
:local:
The release manager role in TVM means you are responsible for a few different things:
- Preparing release notes
- Preparing your setup
- Preparing for release candidates
- Cutting a release branch
- Informing the community of timing
- Making any necessary code changes in that branch (versions are derived from Git tags, so there are no manual version-number edits)
- Running the voting process for a release
- Creating release candidates
- Calling votes and triaging issues
- Finalizing and posting a release:
- Updating the TVM website
- Finalizing release notes
- Announcing the release
Versioning
----------
TVM's version is derived automatically from the most recent Git tag by
`setuptools_scm <https://setuptools-scm.readthedocs.io/>`_ at build time (configured
under ``[tool.setuptools_scm]`` in ``pyproject.toml``). There are **no version numbers
to edit by hand** in ``pyproject.toml``, ``python/tvm/libinfo.py`` or
``include/tvm/runtime/base.h``; releasing is driven entirely by pushing Git tags:
- ``main`` carries a ``vMAJOR.MINOR.devN`` tag (e.g. ``v0.7.dev0``). Commits after it
are versioned ``0.7.devN`` where ``N`` is the number of commits since the tag. This
is why the next dev tag must be pushed on ``main`` when a release branch is cut:
without it, follow-up commits would keep deriving their version from the previous
cycle's tag.
- A release branch (e.g. ``v0.6``) is tagged ``v0.6.0.rc0`` for a candidate (wheel
version ``0.6.0rc0``) and ``v0.6.0`` for the formal release (wheel version
``0.6.0``). Release wheels are built on the exact tag, so the version is the tag
itself.
The legacy ``version.py`` stamping script has been removed. ``web/package.json`` (npm,
a separate ecosystem) is no longer auto-stamped; bump it by hand when starting a new dev
cycle or cutting a release. ``docs/conf.py`` reads ``tvm.__version__`` directly.
Prepare the Release Notes
-------------------------
Release note contains new features, improvement, bug fixes, known issues and deprecation, etc. TVM provides `monthly dev report <https://discuss.tvm.apache.org/search?q=TVM%20Monthly%20%23Announcement>`_ collects developing progress each month. It could be helpful to who writes the release notes.
It is recommended to open a GitHub issue to collect feedbacks for the release note draft before cutting the release branch. See the scripts in ``tests/scripts/release`` for some starting points.
Prepare the Release Candidate
-----------------------------
There may be some code changes necessary on the release branch before the release (for
example cherry-picked fixes). Version numbers are derived from the release tags (see
`Versioning`_), so there are no version numbers to update by hand.
Prepare the GPG Key
-------------------
You can skip this section if you have already uploaded your key.
After generating the gpg key, you need to upload your key to a public key server. Please refer to https://www.apache.org/dev/openpgp.html#generate-key for details.
If you want to do the release on another machine, you can transfer your gpg key to that machine via the ``gpg --export`` and ``gpg --import`` commands.
The last step is to update the KEYS file with your code signing key https://www.apache.org/dev/openpgp.html#export-public-key. Check in the changes to the TVM main branch, as well as ASF SVN,
.. code-block:: bash
# the --depth=files will avoid checkout existing folders
svn co --depth=files "https://dist.apache.org/repos/dist/dev/tvm" svn-tvm
cd svn-tvm
# edit KEYS file
svn ci --username $ASF_USERNAME --password "$ASF_PASSWORD" -m "Update KEYS"
# update downloads.apache.org (note that only PMC members can update the dist/release directory)
svn rm --username $ASF_USERNAME --password "$ASF_PASSWORD" https://dist.apache.org/repos/dist/release/tvm/KEYS -m "Update KEYS"
svn cp --username $ASF_USERNAME --password "$ASF_PASSWORD" https://dist.apache.org/repos/dist/dev/tvm/KEYS https://dist.apache.org/repos/dist/release/tvm/ -m "Update KEYS"
Cut a Release Candidate
-----------------------
To cut a release candidate for the ``v0.6`` release:
#. On the ``main`` commit that should be the last one included in the release, push the
**next** dev-cycle tag ``v0.7.dev0``. This tag is what makes subsequent ``main``
commits versioned ``0.7.devN`` (see `Versioning`_), so it must be pushed *before*
branching.
#. Cut the release branch off that same commit. Branches are named with the base
release version without the patch, e.g. ``v0.6`` for the ``v0.6.0`` release.
#. Push the first release-candidate tag ``v0.6.0.rc0`` on the release branch. CI then
builds the candidate wheel (version ``0.6.0rc0``) for PyPI/TestPyPI testing. Keep
this tag on a ``v0.6`` branch commit that is **not** also the ``v0.7.dev0``-tagged
branch point: when two tags share one commit, which one ``setuptools_scm`` picks is
fragile, so put the candidate tag on a release-prep commit on the branch.
.. code-block:: bash
git clone https://github.com/apache/tvm.git
cd tvm/
# 1. Tag the next dev cycle on main (drives main's 0.7.devN version),
# on the last commit to be included in the release.
git checkout <last-commit-for-release>
git tag v0.7.dev0
git push origin refs/tags/v0.7.dev0
# 2. Cut the release branch off that same commit.
git branch v0.6
git push --set-upstream origin v0.6
# 3. Tag the first release candidate on the release branch. Keep this tag on a
# release-prep commit, NOT the v0.7.dev0-tagged branch point, so the two tags
# never share a commit.
git checkout v0.6
# ... make any release-prep commits (release notes, etc.) here ...
git tag v0.6.0.rc0
git push origin refs/tags/v0.6.0.rc0
The wheel/distribution version is derived from these tags by ``setuptools_scm`` at build
time, so no source files need editing and you no longer run ``version.py`` to stamp the
version (see `Versioning`_).
Go to the GitHub repositories "releases" tab and click "Draft a new release",
- Verify the release by checking the version numbers and ensuring that TVM can build and run the unit tests.
- Provide the release tag in the form of ``v1.0.0.rc0`` where 0 means it's the first release candidate. The tag must match this pattern ``v[0-9]+\.[0-9]+\.[0-9]+\.rc[0-9]`` exactly!
- Select the commit by clicking Target: branch > Recent commits > $commit_hash
- Copy and paste release note draft into the description box
- Select "This is a pre-release"
- Click "Publish release"
Notice that one can still apply changes to the branch after the cut, while the tag is fixed. If any change is required for this release, a new tag has to be created.
Remove previous release candidate (if applied),
.. code-block:: bash
git push --delete origin v0.6.0.rc1
Create source code artifacts,
.. code-block:: bash
# Replace v0.6.0 with the relevant version
git clone git@github.com:apache/tvm.git apache-tvm-src-v0.6.0
cd apache-tvm-src-v0.6.0
git checkout v0.6
git submodule update --init --recursive
git checkout v0.6.0.rc0
rm -rf .DS_Store
find . -name ".git*" -print0 | xargs -0 rm -rf
cd ..
brew install gnu-tar
gtar -czvf apache-tvm-src-v0.6.0.rc0.tar.gz apache-tvm-src-v0.6.0
Use your GPG key to sign the created artifact. First make sure your GPG is set to use the correct private key,
.. code-block:: bash
$ cat ~/.gnupg/gpg.conf
default-key F42xxxxxxxxxxxxxxx
Create GPG signature as well as the hash of the file,
.. code-block:: bash
gpg --armor --output apache-tvm-src-v0.6.0.rc0.tar.gz.asc --detach-sig apache-tvm-src-v0.6.0.rc0.tar.gz
shasum -a 512 apache-tvm-src-v0.6.0.rc0.tar.gz > apache-tvm-src-v0.6.0.rc0.tar.gz.sha512
Update TVM Version on ``main``
------------------------------
The next dev-cycle tag pushed on ``main`` during the cut (step 1 above, e.g. ``v0.7.dev0``)
is what gives ``main`` its ``0.7.devN`` version — ``setuptools_scm`` derives it from that
tag, so there are **no source version numbers to bump** (the old two-commit ``[Dont Squash]``
bump and the ``python version.py`` stamping step are no longer needed). Make sure that tag
sits on the ``main`` commit immediately after the last one included in the release branch;
it is required so that nightly/dev packages built from ``main`` carry the correct
``0.7.devN`` version.
Upload the Release Candidate
----------------------------
Edit the release page on GitHub and upload the artifacts created by the previous steps.
The release manager also needs to upload the artifacts to ASF SVN,
.. code-block:: bash
# the --depth=files will avoid checkout existing folders
svn co --depth=files "https://dist.apache.org/repos/dist/dev/tvm" svn-tvm
cd svn-tvm
mkdir tvm-v0.6.0-rc0
# copy files into it
svn add tvm-0.6.0-rc0
svn ci --username $ASF_USERNAME --password "$ASF_PASSWORD" -m "Add RC"
Cherry-Picking
--------------
After a release branch has been cut but before the release has been voted on, the release manager may cherry-pick commits from ``main``. Since release branches are protected on GitHub, to merge this fixes into the release branch (e.g. ``v0.11``), the release manager must file a PR with the cherry-picked changes against the release branch. The PR should roughly match the original one from ``main`` with extra details on why the commit is being cherry-picked. The community then goes through a normal review and merge process for these PRs. Note that these PRs against the release branches must be `signed <https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits>`_.
Call a Vote on the Release Candidate
------------------------------------
The first voting takes place on the Apache TVM developers list (dev@tvm.apache.org). To get more attention, one can create a GitHub issue start with "[VOTE]" instead, it will be mirrored to dev@ automatically. Look at past voting threads to see how this proceeds. The email should follow this format.
- Provide the link to the draft of the release notes in the email
- Provide the link to the release candidate artifacts
- Make sure the email is in text format and the links are correct
For the dev@ vote, there must be at least 3 binding +1 votes and more +1 votes than -1 votes. Once the vote is done, you should also send out a summary email with the totals, with a subject that looks something like [VOTE][RESULT] ....
In ASF, votes are open at least 72 hours (3 days). If you don't get enough number of binding votes within that time, you cannot close the voting deadline. You need to extend it.
If the vote fails, the community needs to modify the release accordingly: create a new release candidate and re-run the voting process.
Post the Release
----------------
After the vote passes, to upload the binaries to Apache mirrors, you move the binaries from dev directory (this should be where they are voted) to release directory. This "moving" is the only way you can add stuff to the actual release directory. (Note: only PMC can move to release directory)
.. code-block:: bash
export SVN_EDITOR=vim
svn mkdir https://dist.apache.org/repos/dist/release/tvm
svn mv https://dist.apache.org/repos/dist/dev/tvm/tvm-v0.6.0-rc2 https://dist.apache.org/repos/dist/release/tvm/tvm-v0.6.0
# If you've added your signing key to the KEYS file, also update the release copy.
svn co --depth=files "https://dist.apache.org/repos/dist/release/tvm" svn-tvm
curl "https://dist.apache.org/repos/dist/dev/tvm/KEYS" > svn-tvm/KEYS
(cd svn-tvm && svn ci --username $ASF_USERNAME --password "$ASF_PASSWORD" -m"Update KEYS")
Remember to create a new release TAG (v0.6.0 in this case) on GitHub and remove the pre-release candidate TAG.
.. code-block:: bash
git push --delete origin v0.6.0.rc2
Update the TVM Website
----------------------
The website repository is located at `https://github.com/apache/tvm-site <https://github.com/apache/tvm-site>`_. Modify the download page to include the release artifacts as well as the GPG signature and SHA hash. Since TVM's docs are continually updated, upload a fixed version of the release docs. If CI has deleted the docs from the release by the time you go to update the website, you can restart the CI build for the release branch on Jenkins. See the example code below for a starting point.
.. code-block:: bash
git clone https://github.com/apache/tvm-site.git
pushd tvm-site
git checkout asf-site
pushd docs
# make release docs directory
mkdir v0.9.0
pushd v0.9.0
# download the release docs from CI
# find this URL by inspecting the CI logs for the most recent build of the release branch
curl -LO https://tvm-jenkins-artifacts-prod.s3.us-west-2.amazonaws.com/tvm/v0.9.0/1/docs/docs.tgz
tar xf docs.tgz
rm docs.tgz
# add the docs and push
git add .
git commit -m "Add v0.9.0 docs"
git push
Afterwards, modify the `downloads page <https://tvm.apache.org/download>`_ to support the latest release. An example of how to do this is `here <https://github.com/apache/tvm-site/pull/38>`_.
Post the Announcement
---------------------
Send out an announcement email to announce@apache.org, and dev@tvm.apache.org. The announcement should include the link to release note and download page.
Patch Releases
--------------
Patch releases should be reserved for critical bug fixes. Patch releases must go through the same process as normal releases, with the option at the release manager's discretion of a shortened release candidate voting window of 24 hours to ensure that fixes are delivered quickly. A patch release is cut purely by tagging the release base branch (e.g. ``v0.11``): push ``v0.11.1.rc0`` for the candidate and ``v0.11.1`` for the formal patch release; no source version numbers need bumping.
+312
View File
@@ -0,0 +1,312 @@
.. 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.
Testing TVM
===========
This page describes how to write and run Python tests for TVM,
including the target parametrization utilities used by CI.
Python Target Parametrization
-----------------------------
Summary
~~~~~~~
For any supported runtime, TVM should produce numerically
correct results. Therefore, when writing unit tests that validate
the numeric output, these unit tests should be run on all supported
runtimes. Since this is a very common use case, TVM has helper
functions to parametrize unit tests such that they will run on all
targets that are enabled and have a compatible device.
A single Python function in the test suite can expand to several
parameterized unit tests, each of which tests a single target device.
In order for a test to be run, all of the following must be true.
- The test exists in a file or directory that has been passed to
`pytest`.
- The pytest marks applied to the function, either explicitly or
through target parametrization, must be compatible with the
expression passed to pytest's `-m` argument.
- For parametrized tests using the `target` fixture, the target must
appear in the environment variable `TVM_TEST_TARGETS`.
- For parametrized tests using the `target` fixture, the build
configuration in `config.cmake` must enable the corresponding
runtime.
Unit-Test File Contents
~~~~~~~~~~~~~~~~~~~~~~~
.. _pytest-marks: https://docs.pytest.org/en/stable/how-to/mark.html
The recommended way to run a test on multiple targets is to parametrize
over ``target`` with ``@pytest.mark.parametrize``. Tag each GPU target
with ``pytest.mark.gpu`` so the CI routes it to a GPU node, skip a target
that cannot run on the current machine with
:py:func:`tvm.testing.device_enabled`, and obtain its device with
``tvm.device(target)``. The function is run once per target, the
success/failure of each is reported separately, and a target whose device
is disabled in ``config.cmake`` or absent from the machine is reported as
skipped.
.. code-block:: python
@pytest.mark.parametrize(
"target",
["llvm", pytest.param("cuda", marks=pytest.mark.gpu)],
)
def test_function(target):
if not tvm.testing.device_enabled(target):
pytest.skip(f"{target} not enabled")
dev = tvm.device(target)
# Test code goes here
For a test that only applies to a single target, omit the parametrization
and gate the test with ``@pytest.mark.skipif`` (plus ``@pytest.mark.gpu``
for a GPU target):
.. code-block:: python
@pytest.mark.gpu
@pytest.mark.skipif(
not tvm.testing.device_enabled("cuda"), reason="cuda not enabled"
)
def test_function():
target = "cuda"
dev = tvm.device(target)
# Test code goes here
To exclude a target, leave it out of the parametrize list. To mark a
target as expected to fail, wrap it with
``pytest.param("target", marks=pytest.mark.xfail(reason=...))``.
Additional parameters can be combined with the target parametrization by
stacking ``@pytest.mark.parametrize`` decorators, or by listing tuples of
arguments. Tag the GPU rows with ``pytest.mark.gpu`` and skip in the body
as above:
.. code-block:: python
@pytest.mark.parametrize("target,impl", [
("llvm", cpu_implementation),
pytest.param("cuda", gpu_implementation_small_batch, marks=pytest.mark.gpu),
pytest.param("cuda", gpu_implementation_large_batch, marks=pytest.mark.gpu),
])
def test_function(target, impl):
if not tvm.testing.device_enabled(target):
pytest.skip(f"{target} not enabled")
dev = tvm.device(target)
# Test code goes here
Tests gate on hardware and carry metadata using
`pytest marks <pytest-marks>`_. The most frequently applied
marks are as follows.
- ``@pytest.mark.gpu`` - Tags a function as using GPU
capabilities. This has no effect on its own, but can be paired with
the command-line arguments ``-m gpu`` or ``-m 'not gpu'`` to restrict
which tests pytest will execute. Apply it to any test that needs a
GPU so that the CI runs it only on GPU nodes.
- ``@pytest.mark.skipif(not tvm.testing.env.has_X(), reason=...)`` -
Skips a test when a required runtime or hardware feature is not
available. The :py:mod:`tvm.testing.env` module exposes one memoized
probe per capability (e.g. ``has_cuda()``, ``has_rocm()``,
``has_vulkan()``, ``has_gpu()``, ``has_llvm()``), each of which
returns ``False`` when the runtime is disabled in ``config.cmake`` or
no compatible device is present. Pair it with ``@pytest.mark.gpu``
for tests that use the GPU::
@pytest.mark.gpu
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
def test_cuda_vectorize_add():
# Test code goes here
- ``pytest.importorskip("package_name")`` - Skips a test (or the whole
module, when called at import time) if an optional Python package is
not installed. Use this instead of a ``skipif`` for package
dependencies.
Tests that execute on a local GPU must put the complete live-device
lifetime in a small callback passed to
:py:func:`tvm.testing.run_with_gpu_lock`. Target construction and
compilation remain outside so that pytest-xdist workers can compile in
parallel. Device creation, allocation, execution, synchronization,
host conversion, result checks, and child-process teardown remain inside
the callback so no device-backed object outlives the lock.
.. code-block:: python
@pytest.mark.gpu
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
def test_cuda_add_one():
target = tvm.target.Target("cuda -arch=sm_90")
executable = tvm.compile(make_add_one_module(), target)
host_input = np.arange(16, dtype="float32")
def run_and_check():
dev = tvm.cuda(0)
device_input = tvm.runtime.tensor(host_input, dev)
device_output = tvm.runtime.empty(host_input.shape, "float32", dev)
executable(device_input, device_output)
dev.sync()
tvm.testing.assert_allclose(device_output.numpy(), host_input + 1)
tvm.testing.run_with_gpu_lock(run_and_check)
The wrapper uses the existing :py:class:`tvm_ffi.utils.FileLock` with a
persistent machine-local path. A process exit releases the kernel lock;
the remaining file is not stale ownership. Test startup must never
delete or rotate it, because another process could then lock a different
inode. Set ``TVM_TEST_LOCK_DIR`` only when all cooperating processes need
an explicitly configured shared machine-local directory. The default
temporary path coordinates processes running as the same user. Multi-user
runners sharing a GPU must use one administrator-provisioned directory and
persistent lock file that every contender can write, or enforce exclusivity
through the runner. A per-user lock path cannot protect a GPU shared across
users because each user would lock a different file.
There also exists a ``tvm.testing.enabled_targets()`` that returns
all targets that are enabled and runnable on the current machine,
based on the environment variable ``TVM_TEST_TARGETS``, the build
configuration, and the physical hardware present. Some legacy tests
explicitly loop over the targets returned from ``enabled_targets()``,
but this style should not be used for new tests. The pytest output
for this style silently skips runtimes that are disabled in
``config.cmake``, or do not have a device on which they can run. In
addition, the test halts on the first target to fail, which is
ambiguous as to whether the error occurs on a particular target, or on
every target.
.. code-block:: python
# Old style, do not use.
def test_function():
for target, dev in tvm.testing.enabled_targets():
# Test code goes here
Running Locally
~~~~~~~~~~~~~~~
To run the Python unit tests locally, use the command ``pytest`` in
the ``${TVM_HOME}`` directory.
- Environment variables
- ``TVM_TEST_TARGETS`` should be a semicolon-separated list of
targets to run. If unset, will default to the targets defined in
``tvm.testing.DEFAULT_TEST_TARGETS``.
Note: If ``TVM_TEST_TARGETS`` does not contain any targets that
are both enabled, and have an accessible device of that type,
then the tests will fall back to running on the ``llvm`` target
only.
- ``TVM_LIBRARY_PATH`` should be a path to the ``libtvm.so``
library. This can be used, for example, to run tests using a
debug build. If unset, will search for ``libtvm.so`` relative to
the TVM source directory.
- Command-line arguments
- Passing a path to a folder or file will run only the unit tests
in that folder or file. This can be useful, for example, to
avoid running tests located in ``tests/python/contrib`` on a
system without a specific backend installed.
- The ``-m`` argument only runs unit tests that are tagged with a
specific pytest marker. The most frequent usage is to use
``-m gpu`` to run only tests that are marked with
``@pytest.mark.gpu`` and use a GPU to run. It can also be used
to run only tests that do not use a GPU, by passing ``not gpu``
as the marker expression to ``-m``.
Note: This filtering takes place after the selection of targets
based on the ``TVM_TEST_TARGETS`` environment variable. Even if
``-m gpu`` is specified, if ``TVM_TEST_TARGETS`` does not
contain GPU targets, no GPU tests will be run.
Running in a Local Docker Container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _tlcpack: https://hub.docker.com/u/tlcpack
The ``docker/bash.sh`` script can be used to run unit tests inside the
same docker image as is used by the CI. The first argument should
specify which docker image to run (e.g. ``docker/bash.sh ci_gpu``).
Allowed image names are defined in ``ci/jenkins/data.py`` in the TVM source directory,
and map to images at `tlcpack`_.
If no additional arguments are given, the docker image will be loaded
with an interactive bash session. If a script is passed as an
optional argument (e.g. ``docker/bash.sh ci_gpu tests/scripts/task_python_unittest.sh``), then that script will be
executed inside the docker image.
Note: The docker images contain all system dependencies, but do not
include the ``build/config.cmake`` configuration file for those
systems. The TVM source directory is used as the home directory of
the docker image, and so this will default to using the same
config/build directories as the local config. One solution is to
maintain separate ``build_local`` and ``build_docker`` directories,
and make a symlink from ``build`` to the appropriate folder when
entering/exiting docker.
Running in CI
~~~~~~~~~~~~~
Everything in the CI starts from the task definitions present in the
Jenkinsfile. This includes defining which docker image gets used,
what the compile-time configuration is, and which tests are included
in which stages.
- Docker images
Each task of the Jenkinsfile (e.g. 'BUILD: CPU') makes calls to
``docker/bash.sh``. The argument following the call to
docker/bash.sh defines the docker image in CI, just as it does
locally.
- Compile-time configuration
The docker image does not have the ``config.cmake`` file built into
it, so this is the first step in each of the ``BUILD`` tasks. This
is done using the ``tests/scripts/task_config_build_*.sh`` scripts.
Which script is used depends on the build being tested, and is
specified in the Jenkinsfile.
Each ``BUILD`` task concludes by packing a library for use in later
tests.
- Which tests run
The ``Unit Test`` stage of the Jenkinsfile determines how ``pytest``
is called. Each task starts by unpacking a
compiled library that was previous compiled in the ``BUILD`` stage,
then runs a test script
(e.g. ``tests/scripts/task_python_unittest.sh``). These scripts set
the files/folders and command-line options that are passed to
``pytest``.
Several of these scripts include the ``-m gpu`` option, which
restricts the tests to only run tests that include the
``@pytest.mark.gpu`` mark.
+73
View File
@@ -0,0 +1,73 @@
.. 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.
.. _relax-abstraction:
Graph Abstraction for ML Models
-------------------------------
Graph abstraction is a key technique used in machine learning (ML) compilers
to represent and reason about the structure and data flow of ML models. By
abstracting the model into a graph representation, the compiler can perform
various optimizations to improve performance and efficiency. This tutorial will
cover the basics of graph abstraction, its key elements of Relax IR, and how it enables optimization in ML compilers.
What is Graph Abstraction?
~~~~~~~~~~~~~~~~~~~~~~~~~~
Graph abstraction is the process of representing an ML model as a directed graph,
where the nodes represent computational operations (e.g., matrix multiplication,
convolution) and the edges represent the flow of data between these operations.
This abstraction allows the compiler to analyze the dependencies and
relationships between different parts of the model.
.. code:: python
from tvm.script import relax as R
@R.function
def main(
x: R.Tensor((1, 784), dtype="float32"),
weight: R.Tensor((784, 256), dtype="float32"),
bias: R.Tensor((256,), dtype="float32"),
) -> R.Tensor((1, 256), dtype="float32"):
with R.dataflow():
lv0 = R.matmul(x, weight)
lv1 = R.add(lv0, bias)
gv = R.nn.relu(lv1)
R.output(gv)
return gv
Key Features of Relax
~~~~~~~~~~~~~~~~~~~~~
Relax, the graph representation utilized in Apache TVM,
facilitates end-to-end optimization of ML models through several crucial
features:
- **First-class symbolic shape**: Relax employs symbolic shapes to represent
tensor dimensions, enabling global tracking of dynamic shape relationships
across tensor operators and function calls.
- **Multi-level abstractions**: Relax supports cross-level abstractions, from
high-level neural network layers to low-level tensor operations, enabling
optimizations that span different hierarchies within the model.
- **Composable transformations**: Relax offers a framework for composable
transformations that can be selectively applied to different model components.
This includes capabilities such as partial lowering and partial specialization,
providing flexible customization and optimization options.
These features collectively empower Relax to offer a powerful and adaptable approach
to ML model optimization within the Apache TVM ecosystem.
+557
View File
@@ -0,0 +1,557 @@
.. 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.
.. _relax-dpl:
Dataflow Pattern Language (DPL)
===============================
The Dataflow Pattern Language (DPL) is Relax's built-in facility for
**pattern matching and rewriting** on computation graphs. It lets you describe a
sub-graph structure you are looking for, search for it inside a Relax function,
and optionally replace it with a new structure -- all without hand-writing a
full IR visitor.
DPL is used throughout the TVM stack:
- **Operator fusion** -- ``FuseOpsByPattern`` groups matched operators into a
single fused function.
- **Backend dispatch** -- CUTLASS, cuBLAS, cuDNN and other backends register
patterns so the compiler can route sub-graphs to optimized library kernels.
- **Custom graph transforms** -- users write their own patterns and rewriters
to perform project-specific optimizations.
The typical workflow has three steps:
1. **Build a pattern** that describes the sub-graph shape (e.g. ``matmul`` followed
by ``add``).
2. **Match** the pattern against Relax IR to locate all occurrences.
3. **Rewrite** each match into a replacement expression.
The public API lives in ``tvm.relax.dpl`` (source: ``python/tvm/relax/dpl/``).
Building Patterns
-----------------
A *pattern* is a lightweight description of what an expression should look like.
Patterns are built by combining small building blocks.
Basic Patterns
~~~~~~~~~~~~~~
The most common leaf patterns are:
- ``wildcard()`` -- matches any expression.
- ``is_op("relax.add")`` -- matches a specific Relax operator.
- ``is_const()`` -- matches any constant value.
- ``is_var(name)`` -- matches a ``Var`` node (optionally with a given name).
- ``is_dfv(name)`` -- matches a ``DataflowVar`` node.
- ``is_gv(name)`` -- matches a ``GlobalVar``.
.. code:: python
from tvm.relax.dpl import wildcard, is_op, is_const
# Match any relax.add call, regardless of arguments
add_pattern = is_op("relax.add")(wildcard(), wildcard())
Call Patterns
~~~~~~~~~~~~~
Calling a pattern as a function produces a ``CallPattern``. The callee is the
pattern itself, and the positional arguments are patterns for each operand:
.. code:: python
x = wildcard()
w = wildcard()
# Match: relax.matmul(x, w)
matmul = is_op("relax.matmul")(x, w)
For operators with variadic arguments, pass ``varg_default_wildcard=True`` so
that extra arguments are matched by implicit wildcards:
.. code:: python
# Match relax.concat with any number of inputs
concat = is_op("relax.concat")(wildcard(), varg_default_wildcard=True)
DPL also provides specialized helpers for common call patterns:
- ``is_call_tir(func_name, args)`` -- matches ``R.call_tir(func_name, (args...,))``.
- ``is_call_dps_packed(func_name, args)`` -- matches ``R.call_dps_packed``.
- ``is_call_packed(func_name, args)`` -- matches ``R.call_packed``.
.. code:: python
from tvm.relax.dpl import is_call_tir, wildcard
# Match a call_tir that calls the function "decode"
decode = is_call_tir("decode", args=[wildcard(), wildcard()])
Tuple Patterns
~~~~~~~~~~~~~~
``TuplePattern`` matches a Relax tuple with a fixed number of fields.
It supports indexing with ``[]`` to create ``TupleGetItemPattern``:
.. code:: python
from tvm.relax.dpl import is_tuple, wildcard
a, b = wildcard(), wildcard()
tup = is_tuple([a, b])
# Match: getting the first element from the tuple
first = tup[0]
Constraints
~~~~~~~~~~~
Any pattern can be further narrowed by attaching constraints:
- ``.has_dtype(dtype)`` -- the matched expression must have the given data type.
- ``.has_shape(shape)`` -- the matched expression must have the given shape.
- ``.has_attr(attrs)`` -- the matched call must carry the given attributes.
- ``.has_ty(ty)`` -- the matched expression must have the given type.
.. code:: python
# Match a float16 matmul
fp16_matmul = is_op("relax.matmul")(wildcard(), wildcard()).has_dtype("float16")
Logical Combinators
~~~~~~~~~~~~~~~~~~~
Patterns can be combined with logical operators:
- ``pat_a | pat_b`` -- match if **either** pattern matches (``OrPattern``).
- ``pat_a & pat_b`` -- match if **both** patterns match (``AndPattern``).
- ``~pat`` -- match anything **except** ``pat`` (``NotPattern``).
.. code:: python
# Match either relu or gelu activation
activation = is_op("relax.nn.relu")(wildcard()) | is_op("relax.nn.gelu")(wildcard())
Sequence Patterns
~~~~~~~~~~~~~~~~~
When a pattern spans multiple bindings inside a ``DataflowBlock``, use
*sequence operators* to express producer-consumer relationships:
- ``a ^ b`` (``used_by``) -- ``a`` is used by ``b`` (``a`` may also be used
elsewhere).
- ``a >> b`` (``only_used_by``) -- ``a`` is **only** used by ``b`` (no other
consumers).
These return a ``PatternSeq`` that can be chained:
.. code:: python
x = wildcard()
matmul = is_op("relax.matmul")(x, wildcard())
add = is_op("relax.add")(matmul, wildcard())
# matmul result is exclusively consumed by the add
seq = matmul >> add
High-level Helpers
~~~~~~~~~~~~~~~~~~
``make_fused_bias_activation_pattern`` builds a common
``op -> optional bias -> optional activation`` chain in one call:
.. code:: python
from tvm.relax.dpl import make_fused_bias_activation_pattern
# conv2d + bias + relu
pattern = make_fused_bias_activation_pattern(
"relax.nn.conv2d",
with_bias=True,
activation="relax.nn.relu",
)
Matching Without Rewriting
--------------------------
Sometimes you only need to **detect** a structure without replacing it.
Every ``DFPattern`` exposes two matching methods:
- ``pattern.match(expr)`` -- returns ``True`` if the pattern matches.
- ``pattern.extract_matched_expr(expr)`` -- returns a
``dict[DFPattern, Expr]`` mapping each sub-pattern to the concrete
expression it matched, or ``None`` on failure.
.. code:: python
from tvm.relax.dpl import wildcard, is_op
x = wildcard()
y = wildcard()
add_pat = is_op("relax.add")(x, y)
# Assume `expr` is a Relax expression: R.add(a, b)
if add_pat.match(expr):
matched = add_pat.extract_matched_expr(expr)
# matched[x] -> the expression that matched `x`
# matched[y] -> the expression that matched `y`
When matching across variable bindings (e.g., ``lv0 = ...; lv1 = f(lv0)``),
the matcher needs a ``var2val`` map so it can see through binding
boundaries. Use ``tvm.relax.analysis.get_var2val(func)`` to build one:
.. code:: python
from tvm.relax.analysis import get_var2val
var2val = get_var2val(func)
matched = pattern.extract_matched_expr(expr, var2val=var2val)
Rewriting Matched Patterns
--------------------------
``rewrite_call``
~~~~~~~~~~~~~~~~
``rewrite_call`` is the simplest rewrite API. It walks every expression in a
function, and when the pattern matches, it calls your callback to produce a
replacement.
.. code:: python
rewrite_call(pattern, rewriter, func) -> Function
The callback signature is:
.. code:: python
def rewriter(
matched_expr: Expr,
matchings: dict[DFPattern, Expr],
) -> Expr:
...
**Example -- replace** ``reshape(reshape(x, s1), s2)`` **with**
``reshape(x, s2)``:
.. code:: python
from tvm import relax
from tvm.relax.dpl import wildcard, is_op, rewrite_call
inp = wildcard()
shape1, shape2 = wildcard(), wildcard()
inner = is_op("relax.reshape")(inp, shape1)
outer = is_op("relax.reshape")(inner, shape2)
def rewriter(expr, matchings):
# Keep the original input but use the outermost target shape
return relax.op.reshape(matchings[inp], matchings[outer].args[1])
new_func = rewrite_call(outer, rewriter, func)
``rewrite_call`` is best for **local, single-expression** rewrites.
``rewrite_bindings`` with ``PatternContext``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a rewrite involves **multiple bindings** across a ``DataflowBlock``
(e.g., merging three separate matmuls into one), use ``rewrite_bindings``
together with ``PatternContext``.
``PatternContext`` enables topological (graph-level) matching on an entire
dataflow block rather than on individual expressions.
.. code:: python
rewrite_bindings(ctx, rewriter, func) -> Function
The callback receives *variables* rather than expressions:
.. code:: python
def rewriter(
matchings: dict[DFPattern, Var],
bindings: dict[Var, Expr],
) -> dict[Var, Expr]:
...
- ``matchings[pat]`` returns the **bound variable** (``Var``) whose right-hand
side matched ``pat``. The ``Var`` itself carries ``ty`` and can be
used directly in new expressions.
- ``bindings`` maps each ``Var`` to its bound ``Expr`` (the right-hand side),
useful when you need to inspect the original expression.
**Example -- merge three parallel matmuls into one**:
.. code:: python
from tvm.script import relax as R
from tvm.relax.dpl import wildcard, is_op, rewrite_bindings, PatternContext
with PatternContext() as ctx:
inp_pat = wildcard()
w1, w2, w3 = wildcard(), wildcard(), wildcard()
matmul1 = is_op("relax.matmul")(inp_pat, w1)
matmul2 = is_op("relax.matmul")(inp_pat, w2)
matmul3 = is_op("relax.matmul")(inp_pat, w3)
def rewriter(matchings, _bindings):
inp = matchings[inp_pat]
W1 = matchings[w1]
W2 = matchings[w2]
W3 = matchings[w3]
width = W1.ty.shape[1]
concat_w = R.concat([W1, W2, W3], axis=1)
merged = R.matmul(inp, concat_w)
return {
matchings[matmul1]: R.strided_slice(
merged, axes=[2], begin=[0], end=[width],
),
matchings[matmul2]: R.strided_slice(
merged, axes=[2], begin=[width], end=[width * 2],
),
matchings[matmul3]: R.strided_slice(
merged, axes=[2], begin=[width * 2], end=[width * 3],
),
}
new_func = rewrite_bindings(ctx, rewriter, func)
Declarative Rewriting with ``@R.rewriter``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For straightforward one-to-one replacements you can declare the pattern and
its replacement as two Relax functions in a single ``IRModule``. The
``@R.rewriter`` decorator turns the module into a ``PatternMatchingRewriter``
object that can be applied directly.
.. code:: python
from tvm.script import relax as R
@R.rewriter
class RewriteAddToPackedCall:
@R.function
def pattern(
A: R.Tensor([16], "float32"),
B: R.Tensor([16], "float32"),
):
C = R.add(A, B)
return C
@R.function
def replacement(
A: R.Tensor([16], "float32"),
B: R.Tensor([16], "float32"),
):
C = R.call_pure_packed(
"my_fast_add",
A,
B,
ty_args=R.Tensor([16], "float32"),
)
return C
# Apply to an IRModule or a single function
rewritten_mod = RewriteAddToPackedCall(mod)
Composing Rewriters
~~~~~~~~~~~~~~~~~~~
Multiple ``PatternMatchingRewriter`` objects can be combined with the ``|``
operator so they run as a single pass:
.. code:: python
combined = rewriter_a | rewriter_b
result = combined(mod)
The left-hand rewriter is tried first; the right-hand rewriter only applies to
bindings that were **not** already modified by the left.
Using DPL in Compiler Passes
-----------------------------
The most common way DPL appears in the TVM codebase is through the
``FuseOpsByPattern`` pass, which uses ``FusionPattern`` objects to drive
operator fusion.
``FusionPattern``
~~~~~~~~~~~~~~~~~
A ``FusionPattern`` bundles four pieces of information:
- ``name`` -- a string label (e.g., ``"cutlass.matmul"``).
- ``pattern`` -- a ``DFPattern`` that describes the sub-graph to match.
- ``annotation_patterns`` -- a ``dict[str, DFPattern]`` that names interesting
sub-patterns so the check function can inspect them.
- ``check`` -- an optional ``Callable[[PatternCheckContext], bool]`` that
performs additional validation after a structural match succeeds.
.. code:: python
from tvm.relax.dpl import wildcard, is_op
from tvm.relax.transform import FusionPattern
x = wildcard()
w = wildcard()
matmul = is_op("relax.matmul")(x, w)
bias = wildcard()
add = is_op("relax.add")(matmul, bias)
pattern = FusionPattern(
name="my_backend.matmul_bias",
pattern=add,
annotation_patterns={"matmul": matmul, "bias": bias, "lhs": x, "rhs": w},
check=my_check_fn,
)
``PatternCheckContext``
~~~~~~~~~~~~~~~~~~~~~~~
When ``FuseOpsByPattern`` finds a structural match, it calls the ``check``
function with a ``PatternCheckContext`` that provides:
- ``matched_expr`` -- the root expression of the match.
- ``annotated_expr`` -- a ``dict[str, Expr]`` resolved from the
``annotation_patterns``.
- ``matched_bindings`` -- a ``dict[Var, Expr]`` of bindings being fused.
- ``var_usages`` -- a ``dict[Var, Sequence[Var]]`` of variable use chains.
- ``value_to_bound_var`` -- a ``dict[Expr, Var]`` mapping values back to
their bound variables.
Use the check function to enforce constraints that cannot be expressed
structurally (dtype restrictions, shape compatibility, attribute values, etc.):
.. code:: python
from tvm.relax.transform import PatternCheckContext
def my_check_fn(ctx: PatternCheckContext) -> bool:
matmul_expr = ctx.annotated_expr["matmul"]
# Only accept float16 output
if matmul_expr.ty.dtype != "float16":
return False
return True
``FuseOpsByPattern``
~~~~~~~~~~~~~~~~~~~~
``FuseOpsByPattern`` is a module-level pass that takes a list of
``FusionPattern`` (or equivalent tuples) and groups every match into a fused
sub-function.
.. code:: python
from tvm.relax.dpl import wildcard, is_op
from tvm.relax.transform import FuseOpsByPattern
# 1. Define the pattern
w = wildcard()
x = wildcard()
wT = is_op("relax.permute_dims")(w)
o = is_op("relax.matmul")(x, wT)
annotations = {"o": o, "w": w, "x": x, "wT": wT}
def check(ctx):
transpose_call = ctx.annotated_expr["wT"]
ndim = transpose_call.args[0].ty.ndim
if ndim == -1:
return False
if ndim == 2 and transpose_call.attrs.axes is None:
return True
axes = list(range(ndim))
axes[-1], axes[-2] = axes[-2], axes[-1]
return list(transpose_call.attrs.axes) == axes
# 2. Run the pass
mod = FuseOpsByPattern(
[("transpose_matmul_fuse", o, annotations, check)],
bind_constants=False,
)(mod)
When ``annotate_codegen=True``, each fused function is additionally wrapped
with ``Codegen`` and ``global_symbol`` attributes, which is how backends like
CUTLASS and cuBLAS register themselves for external code generation.
Quick Reference
---------------
**Pattern construction**
.. list-table::
:header-rows: 1
:widths: 35 65
* - API
- Description
* - ``wildcard()``
- Match any expression
* - ``is_op(op_name)``
- Match a Relax operator by name
* - ``is_const()``
- Match any constant
* - ``is_var(name)`` / ``is_dfv(name)`` / ``is_gv(name)``
- Match ``Var`` / ``DataflowVar`` / ``GlobalVar``
* - ``is_tuple(fields)``
- Match a tuple with given field patterns
* - ``is_call_tir(name, args)``
- Match ``R.call_tir``
* - ``is_call_dps_packed(name, args)``
- Match ``R.call_dps_packed``
* - ``is_call_packed(name, args)``
- Match ``R.call_packed``
* - ``make_fused_bias_activation_pattern(...)``
- Build ``op + bias + activation`` chain
* - ``.has_dtype()`` / ``.has_shape()`` / ``.has_attr()`` / ``.has_ty()``
- Attach constraints
* - ``|`` / ``&`` / ``~``
- Or / And / Not combinators
* - ``^`` / ``>>``
- used_by / only_used_by (sequence)
**Matching and rewriting**
.. list-table::
:header-rows: 1
:widths: 35 65
* - API
- Description
* - ``pattern.match(expr)``
- Returns ``True`` if pattern matches
* - ``pattern.extract_matched_expr(expr)``
- Returns ``dict[DFPattern, Expr]`` or ``None``
* - ``rewrite_call(pattern, rewriter, func)``
- Rewrite individual expressions
* - ``rewrite_bindings(ctx, rewriter, func)``
- Rewrite across bindings in a ``DataflowBlock``
* - ``PatternMatchingRewriter.from_module(mod)``
- Declarative rewriter from ``IRModule``
* - ``@R.rewriter``
- Decorator shorthand for ``from_module``
**Pass integration**
.. list-table::
:header-rows: 1
:widths: 35 65
* - API
- Description
* - ``FusionPattern(name, pattern, annotations, check)``
- Bundle pattern with metadata for ``FuseOpsByPattern``
* - ``PatternCheckContext``
- Runtime context passed to check functions
* - ``FuseOpsByPattern(patterns, ...)``
- Module pass that fuses matched sub-graphs
+35
View File
@@ -0,0 +1,35 @@
.. 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.
.. _relax-deep-dive:
Relax
=====
Relax is a high-level abstraction for graph optimization and transformation in Apache TVM stack.
Additionally, Apache TVM combines Relax and TensorIR together for cross-level
optimization. Hence, Relax is usually working closely with TensorIR for representing and optimizing
the whole IRModule
.. toctree::
:maxdepth: 2
abstraction
learning
dpl
tutorials/relax_creation
tutorials/relax_transformation
+276
View File
@@ -0,0 +1,276 @@
.. 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.
.. _relax-learning:
Understand Relax Abstraction
============================
Relax is a graph abstraction used in Apache TVM, which
helps to end-to-end optimize ML models. The principal objective of Relax
is to depict the structure and data flow of ML models, including the
dependencies and relationships between different parts of the model, as
well as how to execute the model on hardware.
End to End Model Execution
--------------------------
In this chapter, we will use the following model as an example. This is
a two-layer neural network that consists of two linear operations with
relu activation.
.. image:: /_static/img/e2e_fashionmnist_mlp_model.png
:width: 85%
:align: center
High-Level Operations Representation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let us begin by reviewing a Numpy implementation of the model.
.. code:: python
def numpy_mlp(data, w0, b0, w1, b1):
lv0 = data @ w0 + b0
lv1 = np.maximum(lv0, 0)
lv2 = lv1 @ w1 + b1
return lv2
The above example code shows the high-level array operations to perform the end-to-end model
execution. Of course, we can rewrite the above code using Relax as follows:
.. code:: python
from tvm.script import relax as R
@R.function
def relax_mlp(
data: R.Tensor(("n", 784), dtype="float32"),
w0: R.Tensor((784, 128), dtype="float32"),
b0: R.Tensor((128,), dtype="float32"),
w1: R.Tensor((128, 10), dtype="float32"),
b1: R.Tensor((10,), dtype="float32"),
) -> R.Tensor(("n", 10), dtype="float32"):
with R.dataflow():
lv0 = R.matmul(data, w0) + b0
lv1 = R.nn.relu(lv0)
lv2 = R.matmul(lv1, w1) + b1
R.output(lv2)
return lv2
Low-Level Integration
~~~~~~~~~~~~~~~~~~~~~
However, again from the pov of machine learning compilation (MLC), we would like to see
through the details under the hood of these array computations.
For the purpose of illustrating details under the hood, we will again write examples in low-level numpy:
We will use a loop instead of array functions when necessary to demonstrate the possible loop computations.
When possible, we always explicitly allocate arrays via numpy.empty and pass them around.
The code block below shows a low-level numpy implementation of the same model.
.. code:: python
def lnumpy_linear(X: np.ndarray, W: np.ndarray, B: np.ndarray, Z: np.ndarray):
n, m, K = X.shape[0], W.shape[1], X.shape[1]
Y = np.empty((n, m), dtype="float32")
for i in range(n):
for j in range(m):
for k in range(K):
if k == 0:
Y[i, j] = 0
Y[i, j] = Y[i, j] + X[i, k] * W[k, j]
for i in range(n):
for j in range(m):
Z[i, j] = Y[i, j] + B[j]
def lnumpy_relu0(X: np.ndarray, Y: np.ndarray):
n, m = X.shape
for i in range(n):
for j in range(m):
Y[i, j] = np.maximum(X[i, j], 0)
def lnumpy_mlp(data, w0, b0, w1, b1):
n = data.shape[0]
lv0 = np.empty((n, 128), dtype="float32")
lnumpy_linear(data, w0, b0, lv0)
lv1 = np.empty((n, 128), dtype="float32")
lnumpy_relu0(lv0, lv1)
out = np.empty((n, 10), dtype="float32")
lnumpy_linear(lv1, w1, b1, out)
return out
With the low-level NumPy example in mind, now we are ready to introduce an Relax abstraction
for the end-to-end model execution. The code block below shows a TVMScript implementation of the model.
.. code:: python
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.script import relax as R
@I.ir_module
class Module:
@T.prim_func(private=True)
def linear(x: T.handle, w: T.handle, b: T.handle, z: T.handle):
M, N, K = T.int64(), T.int64(), T.int64()
X = T.match_buffer(x, (M, K), "float32")
W = T.match_buffer(w, (K, N), "float32")
B = T.match_buffer(b, (N,), "float32")
Z = T.match_buffer(z, (M, N), "float32")
Y = T.alloc_buffer((M, N), "float32")
for i, j, k in T.grid(M, N, K):
with T.sblock("Y"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[v_i, v_j] = T.float32(0.0)
Y[v_i, v_j] = Y[v_i, v_j] + X[v_i, v_k] * W[v_k, v_j]
for i, j in T.grid(M, N):
with T.sblock("Z"):
v_i, v_j = T.axis.remap("SS", [i, j])
Z[v_i, v_j] = Y[v_i, v_j] + B[v_j]
@T.prim_func(private=True)
def relu(x: T.handle, y: T.handle):
M, N = T.int64(), T.int64()
X = T.match_buffer(x, (M, N), "float32")
Y = T.match_buffer(y, (M, N), "float32")
for i, j in T.grid(M, N):
with T.sblock("Y"):
v_i, v_j = T.axis.remap("SS", [i, j])
Y[v_i, v_j] = T.max(X[v_i, v_j], T.float32(0.0))
@R.function
def main(
x: R.Tensor(("n", 784), dtype="float32"),
w0: R.Tensor((784, 256), dtype="float32"),
b0: R.Tensor((256,), dtype="float32"),
w1: R.Tensor((256, 10), dtype="float32"),
b1: R.Tensor((10,), dtype="float32")
) -> R.Tensor(("n", 10), dtype="float32"):
cls = Module
n = T.int64()
with R.dataflow():
lv = R.call_tir(cls.linear, (x, w0, b0), out_ty=R.Tensor((n, 256), dtype="float32"))
lv1 = R.call_tir(cls.relu, (lv,), out_ty=R.Tensor((n, 256), dtype="float32"))
lv2 = R.call_tir(cls.linear, (lv1, w1, b1), out_ty=R.Tensor((n, 10), dtype="float32"))
R.output(lv2)
return lv2
The above code contains kinds of functions: the primitive tensor functions (``T.prim_func``) and a
``R.function`` (relax function). Relax function is a new type of abstraction representing
high-level neural network executions.
Note that the above relax module natively supports symbolic shapes, see the ``"n"`` in the
tensor shapes in ``main`` function and ``M``, ``N``, ``K`` in the ``linear`` function. This is
a key feature of Relax abstraction, which enables the compiler to track dynamic shape relations
globally across tensor operators and function calls.
Again it is helpful to see the TVMScript code and low-level numpy code side-by-side and check the
corresponding elements, and we are going to walk through each of them in detail. Since we already
learned about primitive tensor functions, we are going to focus on the high-level execution part.
Key Elements of Relax
---------------------
This section will introduce the key elements of Relax abstraction and how it enables optimization
in ML compilers.
Type
~~~~
Type is the Relax representation of expression type information. It can
be ``TensorType``, ``TupleType``, etc. In the above example, we use ``TensorType``
(short in ``R.Tensor`` in TVMScript) to represent the shape and dtype of the tensor of the inputs,
outputs, and intermediate results.
R.call_tir
~~~~~~~~~~
The ``R.call_tir`` function is a new abstraction in Relax that allows calling primitive tensor
functions in the same IRModule. This is a key feature of Relax that enables cross-level
abstractions, from high-level neural network layers to low-level tensor operations.
Taking one line from the above code as an example:
.. code:: python
lv = R.call_tir(cls.linear, (x, w0, b0), out_ty=R.Tensor((n, 256), dtype="float32"))
To explain what does ``R.call_tir`` work, let us review an equivalent low-level numpy
implementation of the operation, as follows:
.. code:: python
lv0 = np.empty((n, 256), dtype="float32")
lnumpy_linear(x, w0, b0, lv0)
Specifically, ``call_tir`` allocates an output tensor res, then pass the inputs and the output
to the prim_func. After executing prim_func the result is populated in res, then we can return
the result.
This convention is called **destination passing**, The idea is that input and output are explicitly
allocated outside and passed to the low-level primitive function. This style is commonly used
in low-level library designs, so higher-level frameworks can handle that memory allocation
decision. Note that not all tensor operations can be presented in this style (specifically,
there are operations whose output shape depends on the input). Nevertheless, in common practice,
it is usually helpful to write the low-level function in this style when possible.
Dataflow Block
~~~~~~~~~~~~~~
Another important element in a relax function is the R.dataflow() scope annotation.
.. code:: python
with R.dataflow():
lv = R.call_tir(cls.linear, (x, w0, b0), out_ty=R.Tensor((n, 256), dtype="float32"))
lv1 = R.call_tir(cls.relu, (lv,), out_ty=R.Tensor((n, 256), dtype="float32"))
lv2 = R.call_tir(cls.linear, (lv1, w1, b1), out_ty=R.Tensor((n, 10), dtype="float32"))
R.output(lv2)
Before we talk about the dataflow block, let us first introduce the concept of **pure** and
**side-effect**. A function is **pure** or **side-effect free** if:
- it only reads from its inputs and returns the result via its output
- it will not change other parts of the program (such as incrementing a global counter).
For example, all ``R.call_tir`` functions are pure functions, as they only read from their inputs
and write the output to another new allocated tensor. However, the **inplace operations** are not
pure functions, in other words, they are side-effect functions, because they will change the existing
intermediate or input tensors.
A dataflow block is a way for us to mark the computational graph regions of the program.
Specifically, within a dataflow block, all the operations need to be **side-effect free**.
Outside a dataflow block, the operations can contain side-effect.
.. note::
A common question that arises is why we need to manually mark dataflow blocks instead of
automatically inferring them. There are two main reasons for this approach:
- Automatic inference of dataflow blocks can be challenging and imprecise, particularly
when dealing with calls to packed functions (such as cuBLAS integrations). By manually
marking dataflow blocks, we enable the compiler to accurately understand and optimize
the program's dataflow.
- Many optimizations can only be applied within dataflow blocks. For instance, fusion
optimization is limited to operations within a single dataflow block. If the compiler
were to incorrectly infer dataflow boundaries, it might miss crucial optimization
opportunities, potentially impacting the program's performance.
By allowing manual marking of dataflow blocks, we ensure that the compiler has the most
accurate information to work with, leading to more effective optimizations.
@@ -0,0 +1,2 @@
Deep Dive: Relax
----------------
@@ -0,0 +1,285 @@
# 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.
# ruff: noqa: E402
"""
.. _relax-creation:
Relax Creation
==============
This tutorial demonstrates how to create Relax functions and programs.
We'll cover various ways to define Relax functions, including using TVMScript,
and relax NNModule API.
"""
######################################################################
# Create Relax programs using TVMScript
# -------------------------------------
# TVMScript is a domain-specific language for representing Apache TVM's
# intermediate representation (IR). It is a Python dialect that can be used
# to define an IRModule, which contains both TensorIR and Relax functions.
#
# In this section, we will show how to define a simple MLP model with only
# high-level Relax operators using TVMScript.
from tvm import relax, topi
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T
@I.ir_module
class RelaxModule:
@R.function
def forward(
data: R.Tensor(("n", 784), dtype="float32"),
w0: R.Tensor((128, 784), dtype="float32"),
b0: R.Tensor((128,), dtype="float32"),
w1: R.Tensor((10, 128), dtype="float32"),
b1: R.Tensor((10,), dtype="float32"),
) -> R.Tensor(("n", 10), dtype="float32"):
with R.dataflow():
lv0 = R.matmul(data, R.permute_dims(w0)) + b0
lv1 = R.nn.relu(lv0)
lv2 = R.matmul(lv1, R.permute_dims(w1)) + b1
R.output(lv2)
return lv2
RelaxModule.show()
######################################################################
# Relax is not only a graph-level IR, but also supports cross-level
# representation and transformation. To be specific, we can directly call
# TensorIR functions in Relax function.
@I.ir_module
class RelaxModuleWithTIR:
@T.prim_func(s_tir=True)
def relu(x: T.handle, y: T.handle):
n = T.int64()
m = T.int64()
X = T.match_buffer(x, (n, m), "float32")
Y = T.match_buffer(y, (n, m), "float32")
for i, j in T.grid(n, m):
with T.sblock("relu"):
vi, vj = T.axis.remap("SS", [i, j])
Y[vi, vj] = T.max(X[vi, vj], T.float32(0))
@R.function
def forward(
data: R.Tensor(("n", 784), dtype="float32"),
w0: R.Tensor((128, 784), dtype="float32"),
b0: R.Tensor((128,), dtype="float32"),
w1: R.Tensor((10, 128), dtype="float32"),
b1: R.Tensor((10,), dtype="float32"),
) -> R.Tensor(("n", 10), dtype="float32"):
n = T.int64()
cls = RelaxModuleWithTIR
with R.dataflow():
lv0 = R.matmul(data, R.permute_dims(w0)) + b0
lv1 = R.call_tir(cls.relu, lv0, R.Tensor((n, 128), dtype="float32"))
lv2 = R.matmul(lv1, R.permute_dims(w1)) + b1
R.output(lv2)
return lv2
RelaxModuleWithTIR.show()
######################################################################
# .. note::
#
# You may notice that the printed output is different from the written
# TVMScript code. This is because we print the IRModule in a standard
# format, while we support syntax sugar for the input
#
# For example, we can combine multiple operators into a single line, as
#
# .. code-block:: python
#
# lv0 = R.matmul(data, R.permute_dims(w0)) + b0
#
# However, the normalized expression requires only one operation in one
# binding. So the printed output is different from the written TVMScript code,
# as
#
# .. code-block:: python
#
# lv: R.Tensor((784, 128), dtype="float32") = R.permute_dims(w0, axes=None)
# lv1: R.Tensor((n, 128), dtype="float32") = R.matmul(data, lv)
# lv0: R.Tensor((n, 128), dtype="float32") = R.add(lv1, b0)
#
######################################################################
# Create Relax programs using NNModule API
# ----------------------------------------
# Besides TVMScript, we also provide a PyTorch-like API for defining neural networks.
# It is designed to be more intuitive and easier to use than TVMScript.
#
# In this section, we will show how to define the same MLP model using
# Relax NNModule API.
from tvm.relax.frontend import nn
class NNModule(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
######################################################################
# After we define the NNModule, we can export it to TVM IRModule via
# ``export_tvm``.
mod, params = NNModule().export_tvm({"forward": {"x": nn.spec.Tensor(("n", 784), "float32")}})
mod.show()
######################################################################
# We can also insert customized function calls into the NNModule, such as
# Tensor Expression(TE), TensorIR functions or other TVM packed functions.
@T.prim_func(s_tir=True)
def tir_linear(x: T.handle, w: T.handle, b: T.handle, z: T.handle):
M = T.int64()
N = T.int64()
K = T.int64()
X = T.match_buffer(x, (M, K), "float32")
W = T.match_buffer(w, (N, K), "float32")
B = T.match_buffer(b, (N,), "float32")
Z = T.match_buffer(z, (M, N), "float32")
for i, j, k in T.grid(M, N, K):
with T.sblock("linear"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Z[vi, vj] = 0
Z[vi, vj] = Z[vi, vj] + X[vi, vk] * W[vj, vk]
for i, j in T.grid(M, N):
with T.sblock("add"):
vi, vj = T.axis.remap("SS", [i, j])
Z[vi, vj] = Z[vi, vj] + B[vj]
class NNModuleWithTIR(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
n = x.shape[0]
# We can call external functions using nn.extern
x = nn.extern(
"env.linear",
[x, self.fc1.weight, self.fc1.bias],
out=nn.Tensor.placeholder((n, 128), "float32"),
)
# We can also call TensorIR via Tensor Expression API in TOPI
x = nn.tensor_expr_op(topi.nn.relu, "relu", [x])
# We can also call other TVM packed functions
x = nn.tensor_ir_op(
tir_linear,
"tir_linear",
[x, self.fc2.weight, self.fc2.bias],
out=nn.Tensor.placeholder((n, 10), "float32"),
)
return x
mod, params = NNModuleWithTIR().export_tvm(
{"forward": {"x": nn.spec.Tensor(("n", 784), "float32")}}
)
mod.show()
######################################################################
# Create Relax programs using Block Builder API
# ---------------------------------------------
# In addition to the above APIs, we also provide a Block Builder API for
# creating Relax programs. It is a IR builder API, which is more
# low-level and widely used in TVM's internal logic, e.g writing a
# customized pass.
bb = relax.BlockBuilder()
n = T.int64()
x = relax.Var("x", R.Tensor((n, 784), "float32"))
fc1_weight = relax.Var("fc1_weight", R.Tensor((128, 784), "float32"))
fc1_bias = relax.Var("fc1_bias", R.Tensor((128,), "float32"))
fc2_weight = relax.Var("fc2_weight", R.Tensor((10, 128), "float32"))
fc2_bias = relax.Var("fc2_bias", R.Tensor((10,), "float32"))
with bb.function("forward", [x, fc1_weight, fc1_bias, fc2_weight, fc2_bias]):
with bb.dataflow():
lv0 = bb.emit(relax.op.matmul(x, relax.op.permute_dims(fc1_weight)) + fc1_bias)
lv1 = bb.emit(relax.op.nn.relu(lv0))
gv = bb.emit(relax.op.matmul(lv1, relax.op.permute_dims(fc2_weight)) + fc2_bias)
bb.emit_output(gv)
bb.emit_func_output(gv)
mod = bb.get()
mod.show()
######################################################################
# Also, Block Builder API supports building cross-level IRModule with both
# Relax functions, TensorIR functions and other TVM packed functions.
bb = relax.BlockBuilder()
with bb.function("forward", [x, fc1_weight, fc1_bias, fc2_weight, fc2_bias]):
with bb.dataflow():
lv0 = bb.emit(
relax.call_dps_packed(
"env.linear",
[x, fc1_weight, fc1_bias],
out_ty=relax.TensorType((n, 128), "float32"),
)
)
lv1 = bb.emit_te(topi.nn.relu, lv0)
tir_gv = bb.add_func(tir_linear, "tir_linear")
gv = bb.emit(
relax.call_tir(
tir_gv,
[lv1, fc2_weight, fc2_bias],
out_ty=relax.TensorType((n, 10), "float32"),
)
)
bb.emit_output(gv)
bb.emit_func_output(gv)
mod = bb.get()
mod.show()
######################################################################
# Note that the Block Builder API is not as user-friendly as the above APIs,
# but it is lowest-level API and works closely with the IR definition. We
# recommend using the above APIs for users who only want to define and
# transform a ML model. But for those who want to build more complex
# transformations, the Block Builder API is a more flexible choice.
######################################################################
# Summary
# -------
# This tutorial demonstrates how to create Relax programs using TVMScript,
# NNModule API, Block Builder API and PackedFunc API for different use cases.
@@ -0,0 +1,142 @@
# 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.
# ruff: noqa: E402
"""
.. _relax-transform:
Transformation
--------------
In this section, we will dive into the transformation of Relax programs.
Transformations is one of the key ingredients of the compilation flows
for optimizing and integrating with hardware backends.
"""
######################################################################
# Let's first create a simple Relax program as what we have done in
# the :ref:`previous section <relax-creation>`.
import tvm
from tvm import IRModule, relax
from tvm.relax.frontend import nn
class NNModule(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
origin_mod, params = NNModule().export_tvm(
{"forward": {"x": nn.spec.Tensor(("n", 784), "float32")}}
)
origin_mod.show()
######################################################################
# Apply transformations
# ~~~~~~~~~~~~~~~~~~~~~
# Passes are the main way to apply transformations to the program.
# We can apply passes to the program. As first step, let's apply
# a built-in pass ``LegalizeOps`` to lower the high-level operators
# into low-level operators.
mod = tvm.relax.transform.LegalizeOps()(origin_mod)
mod.show()
######################################################################
# As we can see from the output, the high-level operators (aka ``relax.op``) in the program
# are replaced by their corresponding low-level operators (aka ``relax.call_tir``).
#
# Then let's trying to apply the operator fusion, which is a wide-used optimization technique
# in ML compilers. Note that in relax, fusion optimizations are done with the collaboration of
# a set of passes. We can apply them in a sequence.
mod = tvm.ir.transform.Sequential(
[
tvm.relax.transform.AnnotateTIROpPattern(),
tvm.relax.transform.FuseOps(),
tvm.relax.transform.FuseTIR(),
]
)(mod)
mod.show()
######################################################################
# As result, we can see that the ``matmul``, ``add`` and ``relu`` operators are fused
# into one kernel (aka one ``call_tir``).
#
# For all built-in passes, please refer to :py:class:`relax.transform`.
#
# Custom Passes
# ~~~~~~~~~~~~~
# We can also define our own passes. Let's take an example of rewriting the ``relu``
# operator to ``gelu`` operator.
#
# First, we need to write a Relax IR Mutator to do the rewriting.
from tvm.relax.expr_functor import PyExprMutator, mutator
@mutator
class ReluRewriter(PyExprMutator):
def __init__(self, mod):
super().__init__(mod)
def visit_call_(self, call: relax.Call) -> relax.Expr:
# visit the relax.Call expr, and only handle the case when op is relax.nn.relu
if call.op.name == "relax.nn.relu":
return relax.op.nn.gelu(call.args[0])
return super().visit_call_(call)
######################################################################
# Then we can write a pass to apply the mutator to the whole module.
@tvm.transform.module_pass(opt_level=0, name="ReluToGelu")
class ReluToGelu: # pylint: disable=too-few-public-methods
def transform_module(self, mod: IRModule, _ctx: tvm.transform.PassContext) -> IRModule:
"""IRModule-level transformation"""
rewriter = ReluRewriter(mod)
for g_var, func in mod.functions_items():
if isinstance(func, relax.Function):
func = rewriter.visit_expr(func)
rewriter.builder_.update_func(g_var, func)
return rewriter.builder_.get()
mod = ReluToGelu()(origin_mod)
mod.show()
######################################################################
# The printed output shows that the ``relax.nn.relu`` operator is
# rewritten to ``relax.nn.gelu`` operator.
#
# For the details of the mutator, please refer to :py:class:`relax.expr_functor.PyExprMutator`.
#
# Summary
# ~~~~~~~
# In this section, we have shown how to apply transformations to the Relax program.
# We have also shown how to define and apply custom transformations.
+72
View File
@@ -0,0 +1,72 @@
.. 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.
.. _tir-abstraction:
Tensor Program Abstraction
--------------------------
Before we dive into the details of TensorIR, let's first introduce what is a primitive tensor
function. Primitive tensor functions are functions that correspond to a single "unit" of
computational operation. For example, a convolution operation can be a primitive tensor function,
and a fused convolution + relu operation can also be a primitive tensor function.
Usually, a typical abstraction for primitive tensor function implementation contains the following
elements: multi-dimensional buffers, loop nests that drive the tensor computations, and finally,
the compute statements themselves.
.. code:: python
from tvm.script import tirx as T
@T.prim_func
def main(
A: T.Buffer((128,), "float32"),
B: T.Buffer((128,), "float32"),
C: T.Buffer((128,), "float32"),
) -> None:
for i in range(128):
with T.sblock("C"):
vi = T.axis.spatial(128, i)
C[vi] = A[vi] + B[vi]
Key Elements of Tensor Programs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The demonstrated primitive tensor function calculates the element-wise sum of two vectors.
The function:
- Accepts three **multi-dimensional buffers** as parameters, and generates one **multi-dimensional
buffer** as output.
- Incorporates a solitary **loop nest** ``i`` that facilitates the computation.
- Features a singular **compute statement** that calculates the element-wise sum of the two
vectors.
Extra Structure in TensorIR
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Crucially, we are unable to execute arbitrary transformations on the program, as certain
computations rely on the loop's sequence. Fortunately, the majority of primitive tensor
functions we focus on possess favorable properties, such as independence among loop iterations.
For instance, the aforementioned program includes block and iteration annotations:
- The **block annotation** ``with T.sblock("C")`` signifies that the block is the fundamental
computation unit designated for scheduling. A block may encompass a single computation
statement, multiple computation statements with loops, or opaque intrinsics such as Tensor
Core instructions.
- The **iteration annotation** ``T.axis.spatial``, indicating that variable ``vi`` is mapped
to ``i``, and all iterations are independent.
While this information isn't crucial for *executing* the specific program, it proves useful when
transforming the program. Consequently, we can confidently parallelize or reorder loops associated
with ``vi``, provided we traverse all the index elements from 0 to 128.
+43
View File
@@ -0,0 +1,43 @@
.. 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.
.. _tensor-ir-deep-dive:
TensorIR
========
TensorIR is one of the core abstractions in the Apache TVM stack, used to
represent and optimize primitive tensor functions.
The TensorIR codebase consists of two modules (split from the former ``tir``):
- **tirx** — Core IR definitions and lowering (PrimFunc, Buffer, SBlock,
expressions, statements, lowering passes).
- **s_tir** (Schedulable TIR) — Schedule primitives, MetaSchedule, DLight,
and tensor intrinsics.
In TVMScript, both modules are accessed via
``from tvm.script import tirx as T``.
.. toctree::
:maxdepth: 2
abstraction
learning
tutorials/tir_creation
tutorials/tir_transformation
tutorials/dlight_gpu_scheduling
tutorials/meta_schedule
+255
View File
@@ -0,0 +1,255 @@
.. 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.
.. _tirx-learning:
Understand TensorIR Abstraction
===============================
TensorIR is the tensor program abstraction in Apache TVM, which is one of the standard
machine learning compilation frameworks. The principal objective of tensor program abstraction
is to depict loops and associated hardware acceleration options, including threading, the
application of specialized hardware instructions, and memory access.
To help our explanations, let us use the following sequence of tensor computations as
a motivating example. Specifically, for two :math:`128 \times 128` matrices ``A`` and ``B``, let us perform the
following two steps of tensor computations.
.. math::
Y_{i, j} &= \sum_k A_{i, k} \times B_{k, j} \\
C_{i, j} &= \mathbb{relu}(Y_{i, j}) = \mathbb{max}(Y_{i, j}, 0)
The above computations resemble a typical primitive tensor function commonly seen in neural networks,
a linear layer with relu activation. We use TensorIR to depict the above computations as follows.
Before we invoke TensorIR, let's use native Python codes with NumPy to show the computation:
.. code:: python
def lnumpy_mm_relu(A: np.ndarray, B: np.ndarray, C: np.ndarray):
Y = np.empty((128, 128), dtype="float32")
for i in range(128):
for j in range(128):
for k in range(128):
if k == 0:
Y[i, j] = 0
Y[i, j] = Y[i, j] + A[i, k] * B[k, j]
for i in range(128):
for j in range(128):
C[i, j] = max(Y[i, j], 0)
With the low-level NumPy example in mind, now we are ready to introduce TensorIR. The code block
below shows a TensorIR implementation of ``mm_relu``. The particular code is implemented in a
language called TVMScript, which is a domain-specific dialect embedded in python AST.
.. code:: python
from tvm.script import tirx as T
@tvm.script.ir_module
class MyModule:
@T.prim_func
def mm_relu(A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32")):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i, j, k in T.grid(128, 128, 128):
with T.sblock("Y"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
vk = T.axis.reduce(128, k)
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
Next, let's invest the elements in the above TensorIR program.
Function Parameters and Buffers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**The function parameters correspond to the same set of parameters on the numpy function.**
.. code:: python
# TensorIR
def mm_relu(A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32")):
...
# NumPy
def lnumpy_mm_relu(A: np.ndarray, B: np.ndarray, C: np.ndarray):
...
Here ``A``, ``B``, and ``C`` takes a type named ``T.Buffer``, which with shape
argument ``(128, 128)`` and data type ``float32``. This additional information
helps possible MLC process to generate code that specializes in the shape and data
type.
**Similarly, TensorIR also uses a buffer type in intermediate result allocation.**
.. code:: python
# TensorIR
Y = T.alloc_buffer((128, 128), dtype="float32")
# NumPy
Y = np.empty((128, 128), dtype="float32")
Loop Iterations
~~~~~~~~~~~~~~~
**There are also direct correspondence of loop iterations.**
``T.grid`` is a syntactic sugar in TensorIR for us to write multiple nested iterators.
.. code:: python
# TensorIR with `T.grid`
for i, j, k in T.grid(128, 128, 128):
...
# TensorIR with `range`
for i in range(128):
for j in range(128):
for k in range(128):
...
# NumPy
for i in range(128):
for j in range(128):
for k in range(128):
...
Computational Block
~~~~~~~~~~~~~~~~~~~
A significant distinction lies in computational statements:
**TensorIR incorporates an additional construct termed** ``T.sblock``.
.. code:: python
# TensorIR
with T.sblock("Y"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
vk = T.axis.reduce(128, k)
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
# NumPy
vi, vj, vk = i, j, k
if vk == 0:
Y[vi, vj] = 0
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
A **block** represents a fundamental computation unit within TensorIR. Importantly,
a block encompasses more information than standard NumPy code. It comprises a set of block axes
``(vi, vj, vk)`` and the computations delineated around them.
.. code:: python
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
vk = T.axis.reduce(128, k)
The above three lines declare the **key properties** about block axes in the following syntax.
.. code:: python
[block_axis] = T.axis.[axis_type]([axis_range], [mapped_value])
These three lines convey the following details:
- They specify the binding of ``vi``, ``vj``, ``vk`` (in this instance, to ``i``, ``j``, ``k``).
- They declare the original range intended for ``vi``, ``vj``, ``vk``
(the 128 in ``T.axis.spatial(128, i)``).
- They announce the properties of the iterators (spatial, reduce).
Block Axis Properties
~~~~~~~~~~~~~~~~~~~~~
Let's delve deeper into the properties of the block axis. These properties signify the axis's
relationship to the computation in progress. The block comprises three axes ``vi``, ``vj``, and
``vk``, meanwhile the block reads the buffer ``A[vi, vk]``, ``B[vk, vj]`` and writes the buffer
``Y[vi, vj]``. Strictly speaking, the block performs (reduction) updates to Y, which we label
as write for the time being, as we don't require the value of Y from another block.
Significantly, for a fixed value of ``vi`` and ``vj``, the computation block yields a point
value at a spatial location of ``Y`` (``Y[vi, vj]``) that is independent of other locations in ``Y``
(with different ``vi``, ``vj`` values). We can refer to ``vi``, ``vj`` as **spatial axes** since
they directly correspond to the start of a spatial region of buffers that the block writes to.
The axes involved in reduction (``vk``) are designated as **reduce axes**.
Why Extra Information in Block
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One crucial observation is that the additional information (block axis range and their properties)
makes the block to be **self-contained** when it comes to the iterations that it is supposed to
carry out independent from the external loop-nest ``i, j, k``.
The block axis information also provides additional properties that help us to validate the correctness of the
external loops that are used to carry out the computation. For example, the above code block will result in an
error because the loop expects an iterator of size 128, but we only bound it to a for loop of size 127.
.. code:: python
# wrong program due to loop and block iteration mismatch
for i in range(127):
with T.sblock("C"):
vi = T.axis.spatial(128, i)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
error here due to iterator size mismatch
...
Sugars for Block Axes Binding
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In situations where each of the block axes is directly mapped to an outer loop iterator,
we can use ``T.axis.remap`` to declare the block axis in a single line.
.. code:: python
# SSR means the properties of each axes are "spatial", "spatial", "reduce"
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
which is equivalent to
.. code:: python
vi = T.axis.spatial(range_of_i, i)
vj = T.axis.spatial(range_of_j, j)
vk = T.axis.reduce (range_of_k, k)
So we can also write the programs as follows.
.. code:: python
@tvm.script.ir_module
class MyModuleWithAxisRemapSugar:
@T.prim_func
def mm_relu(A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32")):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i, j, k in T.grid(128, 128, 128):
with T.sblock("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
@@ -0,0 +1,2 @@
Deep Dive: TensorIR
-------------------
@@ -0,0 +1,316 @@
# 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.
# ruff: noqa: E402, E501
"""
.. _dlight_gpu_scheduling:
DLight: Rule-Based GPU Scheduling
==================================
TIR functions produced by Relax legalization need GPU-specific scheduling — thread binding,
loop tiling, shared memory usage — before they can run efficiently on a GPU. There are two
main approaches in TVM:
- **MetaSchedule**: explores a search space to find the best schedule. High quality, but
compilation takes minutes to hours.
- **DLight**: applies pre-defined scheduling rules deterministically. No tuning required,
compilation completes in seconds. Performance is excellent for well-known patterns
(e.g., GEMM, GEMV in LLM workloads) and fair for the rest.
This tutorial covers how DLight works, what rules are available, how to diagnose scheduling
quality, and how to write custom rules.
.. contents:: Table of Contents
:local:
:depth: 1
"""
######################################################################
# Prepare a Model
# ---------------
# We build a small model with ``nn.Module`` that is rich enough to trigger multiple DLight
# rules: ``Linear`` layers produce GEMM (matrix multiplication) kernels, ``LayerNorm``
# produces a general-reduction kernel, and ``ReLU`` is a simple elementwise op.
import tvm
from tvm import relax, tirx
from tvm.relax.frontend import nn
from tvm.s_tir import dlight as dl
class DemoModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(768, 768)
self.relu = nn.ReLU()
self.norm = nn.LayerNorm(768)
self.fc2 = nn.Linear(768, 256)
def forward(self, x):
x = self.norm(self.relu(self.fc1(x)))
return self.fc2(x)
mod, params = DemoModel().export_tvm({"forward": {"x": nn.spec.Tensor((1, 768), "float32")}})
######################################################################
# Legalize Relax operators into TIR functions so that DLight has concrete kernels to schedule.
device = tvm.cuda(0)
target = tvm.target.Target.from_device(device)
with target:
mod = relax.get_pipeline("zero")(mod)
######################################################################
# At this point every TIR function in ``mod`` is **unscheduled** — it has no thread bindings
# and would not run efficiently on a GPU. Let's see what functions we have:
for gv, func in mod.functions_items():
if isinstance(func, tirx.PrimFunc):
print(f" {gv.name_hint}")
######################################################################
# Basic Usage: ApplyDefaultSchedule
# ---------------------------------
# ``ApplyDefaultSchedule`` is an ``IRModule`` pass. It iterates over every TIR function in the
# module and tries the given rules **in order**. For each function the first rule whose
# ``apply()`` returns a non-``None`` schedule wins; subsequent rules are skipped.
# After scheduling, the function is marked with ``tirx.is_scheduled`` so it won't be
# scheduled again by a later ``ApplyDefaultSchedule`` call.
######################################################################
# Here we use a common subset of rules. The full catalog (including ``LowBatchGEMV``,
# ``Transpose``, ``RMSNorm``) is listed in the next section.
with target:
scheduled_mod = dl.ApplyDefaultSchedule(
dl.gpu.Matmul(), # GEMM: dense matrix multiplication
dl.gpu.GEMV(), # matrix-vector products
dl.gpu.Reduction(), # simple reductions (sum, max, ...)
dl.gpu.GeneralReduction(), # compound reductions (softmax, layer norm, ...)
dl.gpu.Fallback(), # catch-all for anything unmatched above
)(mod)
scheduled_mod.show()
######################################################################
# Compared with the unscheduled IR, you can now see thread bindings
# (``blockIdx.x``, ``threadIdx.x``, ...) and loop transformations in each TIR function.
######################################################################
# Rule Catalog
# ------------
# DLight ships a set of GPU scheduling rules. Each rule is a subclass of
# ``ScheduleRule`` and implements an ``apply(func, target, tunable)`` method that returns
# a ``Schedule`` if the rule matches, or ``None`` to pass.
#
# The built-in GPU rules, roughly from most specific to most general:
#
# .. list-table::
# :header-rows: 1
# :widths: 20 40 40
#
# * - Rule
# - Pattern
# - Typical operators
# * - ``Matmul``
# - GEMM index pattern ``C[S,I,J] += A[S,I,K] * B[S,J,K]``
# - ``nn.Linear``, batched matmul
# * - ``GEMV``
# - Matrix-vector multiply (one dimension is 1)
# - single-batch decode in attention
# * - ``LowBatchGEMV``
# - Low-batch GEMM scheduled with a GEMV strategy
# - small-batch decode
# * - ``Reduction``
# - Simple accumulation ``X[...] += Y[...]``
# - sum, max, argmax
# * - ``GeneralReduction``
# - Spatial dims followed by reduction dims (``S* R*``)
# - softmax, layer norm, RMS norm
# * - ``Transpose``
# - Read/write indices are permutations of each other
# - 2-D transpose
# * - ``RMSNorm``
# - Contains an ``rsqrt`` operation
# - RMS normalization
# * - ``Fallback``
# - Any function (always matches)
# - generic catch-all
#
# **Rule order matters.** ``ApplyDefaultSchedule`` stops at the first match, so:
#
# - Put **specialized** rules first (``Matmul``, ``GEMV``) — they have strict matching
# conditions but produce high-quality schedules.
# - Put **general** rules later (``GeneralReduction``, ``Fallback``) — they match broadly
# but with less optimal schedules.
# - If you put ``Fallback`` first, it would "steal" every function and no specialized
# rule would ever run.
######################################################################
# Diagnosing Schedule Quality
# ---------------------------
# A common question is: *which rule scheduled which function?* ``ApplyDefaultSchedule``
# does not log this directly, but you can figure it out by applying rules one at a time.
#
# **Step 1**: Apply each rule individually and record which functions it claims.
from collections import OrderedDict
rules = OrderedDict(
[
("Matmul", dl.gpu.Matmul()),
("GEMV", dl.gpu.GEMV()),
("LowBatchGEMV", dl.gpu.LowBatchGEMV()),
("Reduction", dl.gpu.Reduction()),
("GeneralReduction", dl.gpu.GeneralReduction()),
("Transpose", dl.gpu.Transpose()),
("RMSNorm", dl.gpu.RMSNorm()),
]
)
rule_assignment = {}
for rule_name, rule in rules.items():
with target:
test_mod = dl.ApplyDefaultSchedule(rule)(mod)
for gv, func in test_mod.functions_items():
if isinstance(func, tirx.PrimFunc) and gv.name_hint not in rule_assignment:
if "tirx.is_scheduled" in func.attrs and func.attrs["tirx.is_scheduled"] == 1:
rule_assignment[gv.name_hint] = rule_name
######################################################################
# **Step 2**: Functions not claimed by any specialized rule will fall through to ``Fallback``.
all_tir_funcs = [
gv.name_hint for gv, func in mod.functions_items() if isinstance(func, tirx.PrimFunc)
]
fallback_funcs = [name for name in all_tir_funcs if name not in rule_assignment]
print("Rule assignments:")
for name, rule_name in sorted(rule_assignment.items()):
print(f" {name:40s} -> {rule_name}")
if fallback_funcs:
print("Handled by Fallback (may have suboptimal performance):")
for name in sorted(fallback_funcs):
print(f" {name}")
######################################################################
# If an important kernel lands in the Fallback bucket, you have three options:
#
# 1. Write a **custom DLight rule** for it (see below).
# 2. Use **MetaSchedule** to auto-tune that specific function.
# 3. Manually schedule it with the ``tvm.s_tir.Schedule`` API.
######################################################################
# DLight vs MetaSchedule
# ----------------------
# The two systems are complementary, not competing:
#
# .. list-table::
# :header-rows: 1
# :widths: 20 40 40
#
# * -
# - DLight
# - MetaSchedule
# * - Mechanism
# - Deterministic rule matching
# - Search-space exploration
# * - Compile time
# - Seconds
# - Minutes to hours
# * - Performance
# - Excellent on known patterns, fair otherwise
# - Near-optimal with sufficient search budget
# * - Best for
# - Default path, rapid iteration, CI
# - Hot-spot tuning in production
#
# A practical workflow:
#
# 1. Run ``ApplyDefaultSchedule`` with the full rule set to cover all functions.
# 2. Profile the compiled model to identify hot-spot kernels.
# 3. Use ``MetaScheduleTuneTIR`` to auto-tune only those kernels.
#
# Note that ``MetaScheduleTuneTIR`` does **not** automatically skip functions already
# scheduled by DLight — it processes every ``PrimFunc`` in the module. In practice this
# is harmless (tuning an already-scheduled function simply re-explores its space), but if
# you want to avoid the extra search cost, filter the module or use ``MetaScheduleTuneIRMod``
# with ``op_names`` to target specific functions.
######################################################################
# Writing a Custom Rule
# ---------------------
# You can extend DLight by writing your own ``ScheduleRule``. The simplest way is
# ``ScheduleRule.from_callable``, which wraps a plain function into a rule **instance**.
from tvm import s_tir
from tvm.s_tir.dlight.analysis import normalize_prim_func
from tvm.s_tir.dlight.base.schedule_rule import ScheduleRule
@ScheduleRule.from_callable("MyTileAndBind")
def my_tile_and_bind(func: tirx.PrimFunc, target: tvm.target.Target, tunable: bool):
"""A minimal rule: for single-block injective functions, tile and bind to GPU threads."""
if not isinstance(func, tirx.PrimFunc):
return None
sch = s_tir.Schedule(func)
# Use normalize_prim_func to get block info with correct spatial/reduction classification.
# This is the same analysis used by built-in DLight rules.
block_infos = normalize_prim_func(sch)
if block_infos is None or len(block_infos) != 1:
return None # only handle single-block functions
info = block_infos[0]
if not info.is_injective():
return None # skip reductions — dom_kind() uses iter_type, not loop kind
loops = sch.get_loops(info.block_rv)
if len(loops) == 0:
return None
fused = sch.fuse(*loops)
bx, tx = sch.split(fused, factors=[None, 256])
sch.bind(bx, "blockIdx.x")
sch.bind(tx, "threadIdx.x")
return sch
######################################################################
# Insert the custom rule into the rule chain. Note that ``from_callable`` returns an
# **instance**, so pass it directly — do not call ``my_tile_and_bind()`` again.
with target:
custom_mod = dl.ApplyDefaultSchedule(
dl.gpu.Matmul(),
dl.gpu.GeneralReduction(),
my_tile_and_bind, # our custom rule, tried before Fallback
dl.gpu.Fallback(),
)(mod)
custom_mod.show()
######################################################################
# To build a production-quality rule, subclass ``ScheduleRule`` directly and implement
# ``apply()`` with full analysis logic (see ``tvm.s_tir.dlight.gpu.Matmul`` for an example).
######################################################################
# Summary
# -------
# - **DLight** provides fast, deterministic GPU scheduling via rule matching.
# - Rules are tried in order; the first match wins. Put specialized rules before general ones.
# - Use the **single-rule probing** technique to diagnose which rule handles each function.
# - Combine DLight with MetaSchedule: DLight for baseline coverage, MetaSchedule for hot-spot tuning.
# - Extend DLight by writing custom ``ScheduleRule`` implementations.
#
# For DLight's role in the broader optimization pipeline, see :ref:`customize_opt`.
@@ -0,0 +1,307 @@
# 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.
# ruff: noqa: E402
"""
.. _meta_schedule_deep_dive:
MetaSchedule: Search-Based Auto-Tuning
=======================================
MetaSchedule is TVM's search-based auto-tuning framework, located in
``python/tvm/s_tir/meta_schedule/``. It explores different TIR schedules
(loop tiling, vectorization, thread binding, etc.) and measures them on real
hardware to find the fastest implementation for each operator.
While **DLight** (see :ref:`dlight_gpu_scheduling`) provides rule-based scheduling with zero
search time, MetaSchedule trades compilation time for better performance by searching over
the space of possible schedules.
.. contents:: Table of Contents
:local:
:depth: 1
"""
######################################################################
# Architecture Overview
# ---------------------
# A MetaSchedule tuning session involves the following components:
#
# - **ExtractedTask**: A unique TIR workload extracted from a Relax IRModule,
# with a ``task_name`` and ``weight`` (call frequency in the graph).
# - **TuneContext**: Container holding all resources for a single tuning task
# (module, target, space generator, search strategy, etc.).
# - **SpaceGenerator** (default: ``PostOrderApply``): Generates the design space
# of possible schedules by applying ``ScheduleRule`` instances to each block.
# - **SearchStrategy** (default: ``EvolutionarySearch``): Explores the design
# space using an evolutionary algorithm guided by a cost model.
# - **CostModel** (default: ``XGBModel``): Predicts schedule performance using
# XGBoost, reducing the number of actual hardware measurements needed.
# Alternatives include ``MLPModel`` (neural network) and ``RandomModel``
# (baseline).
# - **Builder** / **Runner**: Compile and execute candidates on real hardware to
# obtain measured run times.
# - **Database** (default: ``JSONDatabase``): Persistently stores tuning records
# (schedule traces + measured run times) for later retrieval.
# - **TaskScheduler** (default: ``GradientBasedScheduler``): Allocates tuning
# budget across multiple tasks based on their weights and estimated improvement
# potential.
#
# The tuning loop works as follows:
#
# 1. The **TaskScheduler** picks a task to tune.
# 2. The **SpaceGenerator** produces candidate schedules from the design space.
# 3. The **SearchStrategy** selects candidates (guided by the **CostModel**),
# sends them to the **Builder** and **Runner** for measurement.
# 4. Measured results are committed to the **Database** and used to update the
# **CostModel** for the next iteration.
# 5. Repeat until the trial budget is exhausted.
######################################################################
# Prepare a Model
# ---------------
# We reuse a simple model to demonstrate MetaSchedule APIs.
import os
import tempfile
import tvm
from tvm import relax
from tvm.relax.frontend import nn
class DemoModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(256, 10, bias=False)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
input_shape = (1, 784)
mod, params = DemoModel().export_tvm({"forward": {"x": nn.spec.Tensor(input_shape, "float32")}})
device = tvm.cuda(0)
target = tvm.target.Target.from_device(device)
######################################################################
# User-Facing Entry Points
# ------------------------
# MetaSchedule provides several levels of API, from high-level transforms to
# low-level tuning functions.
#
# Transform-Based API (Recommended)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# These are Relax passes that can be composed into a ``Sequential`` pipeline:
#
# - **MetaScheduleTuneIRMod**: Tunes an entire IRModule. Supports ``op_names``
# for selective operator tuning.
# - **MetaScheduleTuneTIR**: Tunes all TIR functions individually (no
# ``op_names`` filtering).
# - **MetaScheduleApplyDatabase**: Applies the best schedules from the tuning
# database. Only replaces functions that have records; the rest are left
# unchanged.
#
# Here is a typical tune-and-apply pipeline:
#
# .. note::
#
# To save CI time and avoid flakiness, we skip the tuning process in CI.
if os.getenv("CI", "") != "true":
with target, tempfile.TemporaryDirectory() as tmp_dir:
tuned_mod = tvm.ir.transform.Sequential(
[
relax.get_pipeline("zero"),
relax.transform.MetaScheduleTuneTIR(
work_dir=tmp_dir,
max_trials_global=300,
),
relax.transform.MetaScheduleApplyDatabase(work_dir=tmp_dir),
]
)(mod)
tuned_mod.show()
######################################################################
# Inspecting Tunable Tasks
# ------------------------
# Before tuning, use ``extract_tasks`` to see what MetaSchedule will tune:
from tvm.s_tir.meta_schedule.relax_integration import extract_tasks
with target:
legalized_mod = relax.get_pipeline("zero")(mod)
tasks = extract_tasks(legalized_mod, target)
for i, task in enumerate(tasks):
print(f"Task {i}: {task.task_name} (weight={task.weight})")
######################################################################
# Each ``ExtractedTask`` has:
#
# - ``task_name``: Derived from the PrimFunc name (e.g., ``"fused_matmul_add_relu"``).
# - ``weight``: How many ``call_tir`` sites invoke this workload. The task
# scheduler uses weights to allocate more budget to frequently-called operators.
# - ``dispatched``: List of candidate TIR modules for this workload.
######################################################################
# Selective Operator Tuning
# -------------------------
# ``MetaScheduleTuneIRMod`` accepts an ``op_names`` parameter to tune only
# operators whose task name contains any of the given strings:
#
# .. code-block:: python
#
# with target:
# mod = tvm.ir.transform.Sequential([
# relax.transform.MetaScheduleTuneIRMod(
# params={},
# work_dir="./tuning_logs",
# max_trials_global=300,
# op_names=["matmul"], # Only tune matmul-related operators
# ),
# relax.transform.MetaScheduleApplyDatabase(work_dir="./tuning_logs"),
# ])(mod)
#
# Operators without tuning records are left unscheduled -- you can apply DLight or
# other rule-based schedules to cover them afterward.
#
# .. note::
#
# ``MetaScheduleTuneTIR`` does not support ``op_names`` filtering. Use
# ``MetaScheduleTuneIRMod`` when you need selective tuning.
######################################################################
# Database
# --------
# When using a fixed ``work_dir``, tuning results are persisted in two
# newline-delimited JSON files:
#
# - ``database_workload.json``: One line per unique workload (structural hash +
# serialized IRModule).
# - ``database_tuning_record.json``: One line per tuning record (workload index +
# schedule trace + measured run times).
#
# Records are appended incrementally as tuning progresses.
#
# Resumption Semantics
# ~~~~~~~~~~~~~~~~~~~~
# When you re-run tuning with the same ``work_dir``, existing records are loaded
# and used as warm-start seeds for the evolutionary search. The tuner does
# **not** skip already-seen workloads entirely -- it starts from a better initial
# population, so re-runs are faster than starting from scratch but still consume
# trials.
#
# Once tuning is done, subsequent compilations only need
# ``MetaScheduleApplyDatabase``:
#
# .. code-block:: python
#
# with target:
# mod = relax.transform.MetaScheduleApplyDatabase(
# work_dir="./tuning_logs"
# )(mod)
#
# Database Implementations
# ~~~~~~~~~~~~~~~~~~~~~~~~
# MetaSchedule ships several database backends:
#
# - **JSONDatabase**: Persistent file-based storage (default). Created
# automatically when you pass ``work_dir``.
# - **MemoryDatabase**: In-memory, non-persistent. Useful for testing.
# - **UnionDatabase**: Queries all sub-databases and returns the globally best
# record.
# - **OrderedUnionDatabase**: Queries sub-databases in order; returns from the
# first one that has a match.
# - **ScheduleFnDatabase**: Wraps a user-provided scheduling function.
######################################################################
# Cross-Model Database Reuse
# --------------------------
# MetaSchedule identifies workloads by their structural hash. If two models
# contain operators with the same shape, dtype, and computation, they share the
# same hash and can reuse tuning records.
#
# module_equality Options
# ~~~~~~~~~~~~~~~~~~~~~~~
# - ``"structural"`` (default): Exact structural match. Safe but strict.
# - ``"anchor-block"``: Match based on the dominant compute block, ignoring
# surrounding context. More permissive -- enables sharing across fused operators
# that have the same core computation but different fusion boundaries.
#
# ``OrderedUnionDatabase`` enables a layered lookup strategy: check a local
# database first, then fall back to a shared team database:
#
# .. code-block:: python
#
# from tvm.s_tir.meta_schedule.database import JSONDatabase, OrderedUnionDatabase
#
# local_db = JSONDatabase(work_dir="./my_tuning_logs")
# shared_db = JSONDatabase(work_dir="/shared/tuning_db")
# combined_db = OrderedUnionDatabase(local_db, shared_db)
#
# with target, combined_db:
# mod = relax.transform.MetaScheduleApplyDatabase()(mod)
######################################################################
# Key Parameters Reference
# ------------------------
#
# .. list-table::
# :header-rows: 1
# :widths: 25 75
#
# * - Parameter
# - Description
# * - ``max_trials_global``
# - Total trial budget shared across all tasks. Set proportional to the
# number of tasks (e.g., 200-500 trials per task for good results).
# * - ``max_trials_per_task``
# - Per-task trial cap. Defaults to ``max_trials_global`` if not set.
# * - ``op_names``
# - List of strings to filter tasks by name (substring match).
# ``MetaScheduleTuneIRMod`` only.
# * - ``work_dir``
# - Directory for database files and logs. Use a fixed path to enable
# persistence and resumption.
# * - ``cost_model``
# - ``"xgb"`` (XGBoost, default), ``"mlp"`` (neural network), or
# ``"random"`` (baseline). Only available via ``tune_relax``.
# * - ``runner``
# - ``"local"`` (default) or an ``RPCRunner`` instance for remote devices.
# Only available via ``tune_relax``.
# * - ``module_equality``
# - ``"structural"`` (default) or ``"anchor-block"`` for more permissive
# cross-model matching. Only available via ``tune_relax``.
######################################################################
# Summary
# -------
# - **MetaSchedule** finds high-quality TIR schedules by searching over the
# design space and measuring on real hardware.
# - Use ``MetaScheduleTuneTIR`` for full-module tuning, or
# ``MetaScheduleTuneIRMod`` with ``op_names`` for selective tuning.
# - Tuning records persist in ``work_dir`` and can be reused across runs and
# models with the same operator shapes.
# - Combine with DLight: use DLight for fast baseline coverage, then MetaSchedule
# for hot-spot tuning (see :ref:`dlight_gpu_scheduling`).
@@ -0,0 +1,289 @@
# 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.
# ruff: noqa: E402
"""
.. _tir-creation:
TensorIR Creation
-----------------
In this section, we will introduce the methods to write a TensorIR function
in Apache TVM. This tutorial presumes familiarity with the fundamental concepts of TensorIR.
If not already acquainted, please refer to :ref:`tirx-learning` initially.
.. note::
This tutorial concentrates on the construction of **standalone** TensorIR functions. The
techniques presented here are not requisite for end users to compile Relax models.
"""
######################################################################
# Create TensorIR using TVMScript
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The most straightforward way to create a TensorIR function via TVMScript.
# TVMScript is a TVM Python dialect that represents TensorIR in TVM.
#
# .. important::
#
# While TVMScript employs Python syntax and AST, ensuring full compatibility
# with Python tools like auto-completion and linting, it is not a native Python
# language and cannot be executed by a Python interpreter.
#
# More precisely, the decorator **@tvm.script** extracts the Python AST from
# the decorated function, subsequently parsing it into TensorIR.
#
# Standard Format
# ***************
# Let's take an example of ``mm_relu`` from :ref:`tirx-learning`. Here is the complete
# format of the ir_module and in TVMScript:
import numpy as np
import tvm_ffi
import tvm
from tvm.script import ir as I
from tvm.script import tirx as T
@I.ir_module
class MyModule:
@T.prim_func(s_tir=True)
def mm_relu(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i in range(128):
for j in range(128):
for k in range(128):
with T.sblock("Y"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
vk = T.axis.reduce(128, k)
T.reads(A[vi, vk], B[vk, vj])
T.writes(Y[vi, vj])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i in range(128):
for j in range(128):
with T.sblock("C"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
T.reads(Y[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
######################################################################
# Concise with Syntactic Sugar
# ****************************
# For ease of writing, we can employ the following syntactic sugar to
# streamline the code:
#
# - Utilize ``T.grid`` to condense nested loops;
# - Employ ``T.axis.remap`` to abbreviate block iterator annotations;
# - Exclude ``T.reads`` and ``T.writes`` for blocks whose content can
# be inferred from the block body;
@I.ir_module
class ConciseModule:
@T.prim_func(s_tir=True)
def mm_relu(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i, j, k in T.grid(128, 128, 128):
with T.sblock("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
######################################################################
# We can use the following code to verify that the two modules are equivalent:
print(tvm_ffi.structural_equal(MyModule, ConciseModule))
######################################################################
# Interactive with Python Variables
# *********************************
# Despite TVMScript not being executed by a Python interpreter, limited
# interaction with Python is feasible. For instance, Python variables can
# be used to ascertain the shape and data type of a TensorIR.
# Python variables
M = N = K = 128
dtype = "float32"
# IRModule in TVMScript
@I.ir_module
class ConciseModuleFromPython:
@T.prim_func(s_tir=True)
def mm_relu(
A: T.Buffer((M, K), dtype),
B: T.Buffer((K, N), dtype),
C: T.Buffer((M, N), dtype),
):
Y = T.alloc_buffer((M, N), dtype)
for i, j, k in T.grid(M, N, K):
with T.sblock("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.cast(T.float32(0), dtype)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(M, N):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.cast(T.float32(0), dtype))
######################################################################
# Check the equivalence:
print(tvm_ffi.structural_equal(ConciseModule, ConciseModuleFromPython))
######################################################################
# TensorIR Function with Dynamic Shapes
# *************************************
# Despite TVMScript not being executed by a Python interpreter, limited
# interaction with Python is feasible. For instance, Python variables can
# be used to ascertain the shape and data type of a TensorIR.
@I.ir_module
class DynamicShapeModule:
@T.prim_func(s_tir=True)
def mm_relu(a: T.handle, b: T.handle, c: T.handle):
# Dynamic shape definition
M = T.int32()
N = T.int32()
K = T.int32()
# Bind the input buffers with the dynamic shapes
A = T.match_buffer(a, [M, K], dtype)
B = T.match_buffer(b, [K, N], dtype)
C = T.match_buffer(c, [M, N], dtype)
Y = T.alloc_buffer((M, N), dtype)
for i, j, k in T.grid(M, N, K):
with T.sblock("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.cast(T.float32(0), dtype)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(M, N):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.cast(T.float32(0), dtype))
######################################################################
# Now let's check the runtime dynamic shape inference:
def evaluate_dynamic_shape(lib: tvm.runtime.Module, m: int, n: int, k: int):
A = tvm.runtime.tensor(np.random.uniform(size=(m, k)).astype("float32"))
B = tvm.runtime.tensor(np.random.uniform(size=(k, n)).astype("float32"))
C = tvm.runtime.tensor(np.zeros((m, n), dtype="float32"))
lib(A, B, C)
return C.numpy()
# Compile lib only once
dyn_shape_lib = tvm.compile(DynamicShapeModule, target="llvm")
# Able to handle different shapes
print(evaluate_dynamic_shape(dyn_shape_lib, m=4, n=4, k=4))
print(evaluate_dynamic_shape(dyn_shape_lib, m=64, n=64, k=128))
######################################################################
# Create TensorIR using Tensor Expression
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Often, the specifics of TensorIR are disregarded in favor of expressing the computation more
# succinctly, leading to the pragmatic generation of TensorIR. This is where Tensor Expression
# (TE) becomes relevant.
#
# Tensor Expression (TE) serves as a domain-specific language delineating a sequence of
# computations through an expression-like API.
#
# .. note::
#
# Tensor Expression comprises two components within the TVM stack: the expression and the
# schedule. The expression is the domain-specific language embodying the computation pattern,
# precisely what we're addressing in this section. Conversely, the TE schedule is the legacy
# scheduling method, has been superseded by the TensorIR schedule in the current TVM stack.
#
# Create Static-Shape Functions
# *****************************
# We use the same example of ``mm_relu`` from the last subsection to demonstrate the
# TE creation method.
from tvm import te
A = te.placeholder((128, 128), "float32", name="A")
B = te.placeholder((128, 128), "float32", name="B")
k = te.reduce_axis((0, 128), "k")
Y = te.compute((128, 128), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="Y")
C = te.compute((128, 128), lambda i, j: te.max(Y[i, j], 0), name="C")
######################################################################
# Here ``te.compute`` takes the signature ``te.compute(output_shape, fcompute)``.
# And the fcompute function describes how we want to compute the value of each
# element ``Y[i, j]`` for a given index:
#
# .. code:: python
#
# lambda i, j: te.sum(A[i, k] * B[k, j], axis=k)
#
# The aforementioned lambda expression encapsulates the computation:
# :math:`Y_{i, j} = \sum_k A_{i, k} \times B_{k, j}`. Upon defining the computation,
# we can formulate a TensorIR function by incorporating the pertinent parameters of interest.
# In this specific instance, we aim to construct a function with two input parameters **A, B**
# and one output parameter **C**.
te_func = te.create_prim_func([A, B, C]).with_attr({"global_symbol": "mm_relu"})
TEModule = tvm.IRModule({"mm_relu": te_func})
TEModule.show()
######################################################################
# Create Dynamic-Shape Functions
# ******************************
# We can also create a dynamic-shape function using Tensor Expression. The only difference
# is that we need to specify the shape of the input tensors as symbolic variables.
# Declare symbolic variables
M, N, K = te.var("m"), te.var("n"), te.var("k")
A = te.placeholder((M, N), "float32", name="A")
B = te.placeholder((K, N), "float32", name="B")
k = te.reduce_axis((0, K), "k")
Y = te.compute((M, N), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="Y")
C = te.compute((M, N), lambda i, j: te.max(Y[i, j], 0), name="C")
dyn_te_func = te.create_prim_func([A, B, C]).with_attr({"global_symbol": "mm_relu"})
DynamicTEModule = tvm.IRModule({"mm_relu": dyn_te_func})
DynamicTEModule.show()
@@ -0,0 +1,177 @@
# 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.
# ruff: noqa: E402
"""
.. _tirx-transform:
Transformation
--------------
In this section, we will get to the main ingredients of the compilation flows -
transformations of primitive tensor functions.
"""
######################################################################
# In the :ref:`previous section <tirx-learning>`, we have given an example of how to write
# ``mm_relu`` using TensorIR. In practice, there can be multiple ways to implement
# the same functionality, and each implementation can result in different performance.
#
# .. note::
# This tutorial primarily illustrates the application of TensorIR Transformation,
# rather than delving into optimization techniques.
#
# First, let's take a look at the implementation of ``mm_relu`` in the previous section:
import tvm
from tvm.script import ir as I
from tvm.script import tirx as T
@I.ir_module
class MyModule:
@T.prim_func(s_tir=True)
def main(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
T.func_attr({"tirx.noalias": True})
with T.sblock("root"):
T.reads()
T.writes()
Y = T.sblock_alloc_buffer((128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
######################################################################
# Before we transform the function, let's first evaluate the performance of the
# original implementation.
import numpy as np
a_np = np.random.uniform(size=(128, 128)).astype("float32")
b_np = np.random.uniform(size=(128, 128)).astype("float32")
c_np = a_np @ b_np
a_nd = tvm.runtime.tensor(a_np)
b_nd = tvm.runtime.tensor(b_np)
c_nd = tvm.runtime.tensor(np.zeros((128, 128), dtype="float32"))
def evaluate(mod: tvm.IRModule):
lib = tvm.tirx.build(mod, target="llvm")
# check correctness
lib(a_nd, b_nd, c_nd)
np.testing.assert_allclose(c_nd.numpy(), c_np, rtol=1e-5)
# evaluate performance
f_timer = lib.time_evaluator("main", tvm.cpu())
print(f_timer(a_nd, b_nd, c_nd))
evaluate(MyModule)
######################################################################
# Initialization Schedule
# ***********************
# We initiate the process of code transformation by establishing a Schedule helper class,
# utilizing the provided **MyModule** as input.
sch = tvm.s_tir.Schedule(MyModule)
######################################################################
# Loop Tiling
# ***********
# Subsequently, we execute the requisite operations to acquire a reference to
# block **Y** and its associated loops.
block_Y = sch.get_sblock("Y")
i, j, k = sch.get_loops(block_Y)
######################################################################
# We now proceed to execute the transformations. The initial modification involves
# splitting loop ``j`` into two separate loops, with the inner loop possessing a
# length of 8. It is crucial to understand that the transformation process is procedural;
# thus, inadvertent execution of the block twice will yield an error stating the
# non-existence of variable ``j``.
j0, j1 = sch.split(j, factors=[None, 8])
######################################################################
# The outcome of the transformation can be examined, as it is retained within ``sch.mod``.
sch.mod.show()
######################################################################
# Following the initial transformation phase, two supplementary loops, ``j_0`` and ``j_1``,
# have been generated with respective ranges of 16 and 8. The subsequent
# action involves reordering these two loops.
sch.reorder(j0, k, j1)
sch.mod.show()
evaluate(sch.mod)
######################################################################
# Leverage Localities
# *******************
# Subsequently, we will execute two additional transformation steps to achieve a different
# variant. First, we employ a primitive known as **reverse_compute_at** to relocate block
# **C** to an inner loop of **Y**.
block_C = sch.get_sblock("C")
sch.reverse_compute_at(block_C, j0)
sch.mod.show()
######################################################################
# Rewrite Reduction
# *****************
# Until now, the reduction initialization and update step have been maintained together
# within a single block body. This amalgamated form facilitates loop transformations,
# as the outer loops ``i``, ``j`` of initialization and updates generally need to remain
# synchronized.
#
# Following the loop transformations, we can segregate the initialization of Y's elements
# from the reduction update via the **decompose_reduction** primitive.
sch.decompose_reduction(block_Y, k)
sch.mod.show()
evaluate(sch.mod)
######################################################################
# Trace the Transformation
# ************************
# TensorIR schedule is a procedural language, and the transformation is executed in a
# step-by-step manner. We can trace the transformation by printing the schedule or the
# history of the schedule.
#
# We've already see the schedule by printing ``sch.mod``. We can also print the history
# of the schedule by ``sch.trace``.
sch.trace.show()
######################################################################
# Alternatively, we can output the IRModule in conjunction with the historical trace.
sch.show()
+318
View File
@@ -0,0 +1,318 @@
# 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.
# pylint: disable=redefined-outer-name, missing-module-docstring
import argparse
import hashlib
import os
import re
from collections.abc import Callable
from html.parser import HTMLParser
from urllib.parse import urlparse
import requests
# NOTE: This script is called by the Makefile via `make htmldepoly`.
# It's not called every time the docs are built on CI. However, it's
# can be only called during deployment stage, instead of building the docs.
# Also, we can download the resources manually before running this script to
# avoid the overhead of downloading the resources every time the docs are built.
# Set to store unique external URLs found during processing
BASE_URL = "https://tvm.apache.org"
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
HTML_DIR = os.path.join(SCRIPT_DIR, "_build/html")
class ExternalURLParser(HTMLParser):
"""HTML Parser to find external URLs in HTML content."""
def __init__(self):
super().__init__()
self.external_urls: list[str] = []
self.base_domain = urlparse(BASE_URL).netloc
# Tags and their attributes that might contain external resources
self.tags_to_check = {
"img": "src",
"script": "src",
"iframe": "src",
"video": "src",
"audio": "src",
"link": "href",
"source": "src",
"embed": "src",
}
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
"""Handle HTML start tags to find external URLs."""
if tag not in self.tags_to_check:
return
attr_name = self.tags_to_check[tag]
for name, value in attrs:
if name != attr_name or not value:
continue
if value.startswith(("http://", "https://")):
domain = urlparse(value).netloc
if domain and domain != self.base_domain:
self.external_urls.append(value)
def detect_html_external_urls(html_content: str) -> list[str]:
"""
Detect third-party embedded resources in HTML content.
Parameters
----------
html_content : str
The HTML content to analyze
Returns
-------
List[str]
List of external URLs found in the HTML content
"""
parser = ExternalURLParser()
parser.feed(html_content)
return parser.external_urls
def detect_css_external_urls(css_content: str) -> list[str]:
"""
Detect external URLs in CSS content.
Parameters
----------
css_content : str
The CSS content to analyze
Returns
-------
List[str]
List of external URLs found in the CSS content
"""
external_urls: list[str] = []
# Regex to find URLs in CSS
url_pattern = re.compile(r'url\(["\']?(.*?)["\']?\)')
matches = url_pattern.findall(css_content)
for match in matches:
if match.startswith(("http://", "https://")) and not match.startswith(BASE_URL):
external_urls.append(match)
return external_urls
def all_files_in_dir(path: str) -> list[str]:
"""
Get a list of all files in a directory and its subdirectories.
Parameters
----------
path : str
The root directory path to search
Returns
-------
List[str]
List of full paths to all files found
"""
return [os.path.join(root, file) for root, _, files in os.walk(path) for file in files]
def detect_urls(files: list[str], verbose: bool = False) -> list[str]:
"""
Detect external URLs in the given HTML and CSS files.
Parameters
----------
files : List[str]
List of file paths to check for external URLs
verbose : bool, optional
Whether to print verbose output, by default False
Returns
-------
List[str]
List of external URLs found in the files
"""
external_urls: set[str] = set()
for file in files:
f_detect: Callable[[str, str], list[str]] | None = None
if file.endswith(".html"):
f_detect = detect_html_external_urls
elif file.endswith(".css"):
f_detect = detect_css_external_urls
else:
continue
with open(file, encoding="utf-8") as f:
content = f.read()
urls = f_detect(content)
if verbose:
print(f"Processing {file}")
exist_urls, new_urls = 0, 0
for url in urls:
if url in external_urls:
exist_urls += 1
else:
new_urls += 1
if verbose:
print(f"Found new {url}")
print(f"Found {exist_urls} existing resources and {new_urls} new resources")
external_urls.update(urls)
if verbose:
print(f"Total {len(external_urls)} external resources")
print("External resources:")
print("\n".join(external_urls))
return list(external_urls)
def download_external_urls(
external_urls: list[str], verbose: bool = False
) -> tuple[dict[str, str], list[str]]:
"""
Download external URLs and save them to docs/_static/downloads.
Parameters
----------
external_urls : List[str]
List of external URLs to download
verbose : bool, optional
Whether to print verbose output, by default False
Returns
-------
Tuple[Dict[str, str], List[str]]
A tuple containing:
- Dictionary mapping original URLs to their downloaded file paths
- List of paths to all downloaded files (including source maps)
"""
download_dir = os.path.join(HTML_DIR, "_static/downloads")
os.makedirs(download_dir, exist_ok=True)
used_file_names: set[str] = set()
downloaded_files: list[str] = []
remap_urls: dict[str, str] = {}
for url in external_urls:
query = urlparse(url).query
if url.startswith("https://fonts.googleapis.com/css2"):
file_name = f"{hashlib.md5(url.encode()).hexdigest()}.css"
elif query:
raise ValueError(f"Unsupported URL with query: {url}")
else:
file_name = urlparse(url).path.split("/")[-1]
if verbose:
print(f"remapping {url} to {file_name}")
if file_name in used_file_names:
raise ValueError(f"File name {file_name} already exists")
used_file_names.add(file_name)
response = requests.get(url, timeout=30)
body = response.content
with open(os.path.join(download_dir, file_name), "wb") as f:
f.write(body)
remap_urls[url] = os.path.join(download_dir, file_name)
downloaded_files.append(os.path.join(download_dir, file_name))
# Also download the sourceMappingURL
if not url.startswith("https://fonts.googleapis.com/css2"):
map_file_name = f"{file_name}.map"
response = requests.get(f"{url}.map", timeout=30)
if response.status_code == 200:
body = response.content
with open(os.path.join(download_dir, map_file_name), "wb") as f:
f.write(body)
if verbose:
print(f"Downloaded {map_file_name} for {url}")
downloaded_files.append(os.path.join(download_dir, map_file_name))
return remap_urls, downloaded_files
def replace_urls_in_files(remap_urls: dict[str, str], verbose: bool = False):
"""
Replace external URLs with their downloaded versions in HTML/CSS files.
Parameters
----------
remap_urls : Dict[str, str]
Dictionary mapping original URLs to their downloaded file paths
verbose : bool, optional
Whether to print verbose output, by default False
"""
for root, _, files in os.walk(HTML_DIR):
for file in files:
if not (file.endswith(".html") or file.endswith(".css")):
continue
file_path = os.path.join(root, file)
if verbose:
print(f"Processing {file_path}")
# Calculate relative path from current file to _static/downloads
rel_path = os.path.relpath(
os.path.join(HTML_DIR, "_static/downloads"), os.path.dirname(file_path)
)
with open(file_path, encoding="utf-8") as f:
content = f.read()
new_content = content
for original_url, new_path in remap_urls.items():
relative_url = os.path.join(rel_path, os.path.basename(new_path))
new_content = new_content.replace(original_url, relative_url)
if new_content != content:
with open(file_path, "w", encoding="utf-8") as f:
f.write(new_content)
if verbose:
print(f"Updated {file_path}")
def download_and_replace_urls(files: list[str] | None = None, verbose: bool = False):
"""
Download external URLs found in files and replace them with local copies.
Recursively processes any new external URLs found in downloaded content.
Parameters
----------
files : Optional[List[str]], optional
List of files to check for external URLs. If None, checks all files under HTML_DIR
verbose : bool, optional
Whether to print verbose output, by default False
"""
if files is None:
files = all_files_in_dir(HTML_DIR)
remap_urls = {}
while True:
external_urls = detect_urls(files, verbose=verbose)
if not external_urls:
break
round_remap_urls, files = download_external_urls(external_urls, verbose=verbose)
remap_urls.update(round_remap_urls)
replace_urls_in_files(remap_urls, verbose=verbose)
if __name__ == "__main__":
args = argparse.ArgumentParser()
args.add_argument("-v", "--verbose", action="store_true")
args.add_argument("-p", "--path", type=str, default=None)
args = args.parse_args()
if args.path is not None:
HTML_DIR = args.path
download_and_replace_urls(verbose=args.verbose)
+73
View File
@@ -0,0 +1,73 @@
.. 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.
TVM Errors
==========
TVM may raise errors from Python code, from C++ code reached through the
FFI, or from generated runtime modules. Error messages usually include
a Python stack trace, and may also include a C++ stack trace when the
error crosses the TVM FFI boundary.
Some errors report invalid user input, unsupported operators, missing
runtime features, or unavailable hardware. Others report a failed
internal check, usually raised by ``TVM_FFI_ICHECK`` or
``TVM_FFI_THROW`` in C++ code. Internal check failures often indicate
that TVM reached a state that the implementation did not expect.
What to Check First
-------------------
- Make sure the TVM Python package and native libraries come from the
same build. A common symptom of a mismatched environment is importing
Python files from one checkout while loading ``libtvm`` from another.
- Check that the required runtime is enabled in ``config.cmake``. For
example, CUDA tests and CUDA compilation require a TVM build with
CUDA support enabled.
- Check that the target hardware is available to the process. GPU
tests may be skipped or fail if the device is not visible inside the
current container or environment.
- If the error occurs while importing or converting a model, reduce the
input to the smallest model, operator, or shape that reproduces the
issue.
Reporting an Issue
------------------
Search the `Apache TVM Discuss Forum <https://discuss.tvm.apache.org/>`_
and the `TVM issue tracker <https://github.com/apache/tvm/issues>`_
for the exact error message first. If you do not find an existing
report, include the following details when starting a new discussion or
filing an issue:
- The TVM version or git commit hash.
- The Python version, operating system, and hardware.
- The target and runtime being used, such as LLVM, CUDA, Vulkan, or RPC.
- The relevant build configuration from ``config.cmake``.
- A minimal script, model, input shape, or IR module that reproduces the
failure.
- The full error message, including both Python and C++ stack traces
when present.
Developer Notes
---------------
For guidance on raising typed errors from TVM code, see
:ref:`error-handling-guide`. That guide covers when to use specific
error types, how C++ error prefixes map to Python exceptions, and how
``TVM_FFI_ICHECK`` interacts with TVM's error handling.
+19
View File
@@ -0,0 +1,19 @@
.. 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.
Index
=====
+66
View File
@@ -0,0 +1,66 @@
.. 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.
Overview
========
Apache TVM is a machine learning compilation framework, following the principle of **Python-first development**
and **universal deployment**. It takes in pre-trained machine learning models,
compiles and generates deployable modules that can be embedded and run everywhere. Apache TVM also enables customizing optimization processes to introduce new optimizations, libraries, codegen
and more.
Key Principle
-------------
- **Python-first**: the optimization process is fully customizable in Python.
It is easy to customize the optimization pipeline without recompiling the TVM stack.
- **Composable**: the optimization process is composable. It is easy to compose
new optimization passes, libraries and codegen to the existing pipeline.
Key Goals
---------
- **Optimize** performance of ML workloads, composing libraries and codegen.
- **Deploy** ML workloads to a diverse set of new environments, including new runtime and new hardware.
- **Continuously improve and customize** ML deployment pipeline in Python by quickly customizing library dispatching,
bringing in customized operators and code generation.
Key Flow
--------
Here is a typical flow of using TVM to deploy a machine learning model. For a runnable example,
please refer to :ref:`quick_start`
1. **Import/construct an ML model**
TVM supports importing models from various frameworks, such as PyTorch and ONNX for generic ML models. Meanwhile, we can create models directly using Relax frontend for scenarios of large language models.
2. **Perform composable optimization** transformations via ``pipelines``
The pipeline encapsulates a collection of transformations to achieve two goals:
- **Graph Optimizations**: such as operator fusion, and layout rewrites.
- **Tensor Program Optimization**: Map the operators to low-level implementations (both library or codegen)
.. note::
The two are goals but not the stages of the pipeline. The two optimizations are performed
**at the same level**, or separately in two stages.
3. **Build and universal deploy**
Apache TVM aims to provide a universal deployment solution to bring machine learning everywhere with every language with minimum runtime support. TVM runtime can work in non-Python environments, so it works on mobile, edge devices or even bare metal devices. Additionally, TVM runtime comes with native data structures, and can also have zero copy exchange with the existing ecosystem (PyTorch, TensorFlow, TensorRT, etc.) using DLPack support.
+2
View File
@@ -0,0 +1,2 @@
Get Started
-----------
+285
View File
@@ -0,0 +1,285 @@
# 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.
# ruff: noqa: E402
"""
.. _ir_module:
IRModule
========
This tutorial presents the core abstraction of Apache TVM, the IRModule.
The IRModule encompasses the **entirety** of the ML models, incorporating the
computational graph, tensor programs, and potential calls to external libraries.
.. contents:: Table of Contents
:local:
:depth: 1
"""
import numpy as np
######################################################################
# Create IRModule
# ---------------
# IRModules can be initialized in various ways. We demonstrate a few of them
# below.
import torch
from torch import nn
from torch.export import export
import tvm
from tvm import relax
from tvm.relax.frontend.torch import from_exported_program
######################################################################
# Import from existing models
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The most common way to initialize an IRModule is to import from an existing
# model. Apache TVM accommodates imports from a range of frameworks,
# such as PyTorch and ONNX. This tutorial solely demonstrates the import process
# from PyTorch.
# Create a dummy model
class TorchModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
# Give an example argument to torch.export
example_args = (torch.randn(1, 784, dtype=torch.float32),)
# Convert the model to IRModule
with torch.no_grad():
exported_program = export(TorchModel().eval(), example_args)
mod_from_torch = from_exported_program(
exported_program, keep_params_as_input=True, unwrap_unit_return_tuple=True
)
mod_from_torch, params_from_torch = relax.frontend.detach_params(mod_from_torch)
# Print the IRModule
mod_from_torch.show()
######################################################################
# Write with Relax NN Module
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# Apache TVM also provides a set of PyTorch-liked APIs, to help users
# write the IRModule directly.
from tvm.relax.frontend import nn
class RelaxModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
mod_from_relax, params_from_relax = RelaxModel().export_tvm(
{"forward": {"x": nn.spec.Tensor((1, 784), "float32")}}
)
mod_from_relax.show()
######################################################################
# Create via TVMScript
# ~~~~~~~~~~~~~~~~~~~~
# TVMScript is a Python-based DSL for IRModules. We are able to
# directly output the IRModule in the TVMScript syntax, or alternatively,
# parse the TVMScript to obtain an IRModule.
from tvm.script import ir as I
from tvm.script import relax as R
@I.ir_module
class TVMScriptModule:
@R.function
def main(
x: R.Tensor((1, 784), dtype="float32"),
fc1_weight: R.Tensor((256, 784), dtype="float32"),
fc1_bias: R.Tensor((256,), dtype="float32"),
fc2_weight: R.Tensor((10, 256), dtype="float32"),
fc2_bias: R.Tensor((10,), dtype="float32"),
) -> R.Tensor((1, 10), dtype="float32"):
R.func_attr({"num_input": 1})
with R.dataflow():
permute_dims = R.permute_dims(fc1_weight, axes=None)
matmul = R.matmul(x, permute_dims, out_dtype=None)
add = R.add(matmul, fc1_bias)
relu = R.nn.relu(add)
permute_dims1 = R.permute_dims(fc2_weight, axes=None)
matmul1 = R.matmul(relu, permute_dims1, out_dtype=None)
add1 = R.add(matmul1, fc2_bias)
gv = add1
R.output(gv)
return gv
mod_from_script = TVMScriptModule
mod_from_script.show()
######################################################################
# Attributes of an IRModule
# -------------------------
# An IRModule is a collection of functions, indexed by GlobalVars.
mod = mod_from_torch
print(mod.get_global_vars())
######################################################################
# We can access the functions in the IRModule by indexing with the GlobalVars
# or their names
# index by global var name
print(mod["main"])
# index by global var, and checking they are the same function
(gv,) = mod.get_global_vars()
assert mod[gv] == mod["main"]
######################################################################
# Transformations on IRModules
# ----------------------------
# Transformations are the import component of Apache TVM. One transformation
# takes in an IRModule and outputs another IRModule. We can apply a sequence of
# transformations to an IRModule to obtain a new IRModule. That is the common way to
# optimize a model.
#
# In this getting started tutorial, we only demonstrate how to apply transformations
# to an IRModule. For details of each transformation, please refer to the
# :ref:`Transformation API Reference <api-relax-transformation>`
######################################################################
# We first apply **LegalizeOps** transformation to the IRModule. This transformation
# will convert the Relax module into a mixed stage, with both Relax and TensorIR function
# within the same module. Meanwhile, the Relax operators will be converted into ``call_tir``.
mod = mod_from_torch
mod = relax.transform.LegalizeOps()(mod)
mod.show()
######################################################################
# After the transformation, there are much more functions inside the module. Let's print
# the global vars again.
print(mod.get_global_vars())
######################################################################
# Next, Apache TVM provides a set of default transformation pipelines for users,
# to simplify the transformation process. We can then apply the default pipeline to the module.
# The default **zero** pipeline contains very fundamental transformations, including:
#
# - **LegalizeOps**: This transform converts the Relax operators into `call_tir` functions
# with the corresponding TensorIR Functions. After this transform, the IRModule will
# contain both Relax functions and TensorIR functions.
# - **AnnotateTIROpPattern**: This transform annotates the pattern of the TensorIR functions,
# preparing them for subsequent operator fusion.
# - **FoldConstant**: This pass performs constant folding, optimizing operations
# involving constants.
# - **FuseOps and FuseTIR**: These two passes work together to fuse operators based on the
# patterns annotated in the previous step (AnnotateTIROpPattern). These passes transform
# both Relax functions and TensorIR functions.
#
# .. note::
#
# Here, we have applied **LegalizeOps** twice in the flow. The second time is useless but
# harmless.
#
# Every passes can be duplicated in the flow, since we ensure the passes can handle all legal
# IRModule inputs. This design can help users to construct their own pipeline.
mod = relax.get_pipeline("zero")(mod)
mod.show()
######################################################################
# Deploy the IRModule Universally
# -------------------------------
# After the optimization, we can compile the model into a TVM runtime module.
# Notably, Apache TVM provides the ability of universal deployment, which means
# we can deploy the same IRModule on different backends, including CPU, GPU, and other emerging
# backends.
#
# Deploy on CPU
# ~~~~~~~~~~~~~
# We can deploy the IRModule on CPU by specifying the target as ``llvm``.
exec = tvm.compile(mod, target="llvm")
dev = tvm.cpu()
vm = relax.VirtualMachine(exec, dev)
raw_data = np.random.rand(1, 784).astype("float32")
data = tvm.runtime.tensor(raw_data, dev)
cpu_out = vm["main"](data, *params_from_torch["main"]).numpy()
print(cpu_out)
######################################################################
# Deploy on GPU
# ~~~~~~~~~~~~~
# Besides, CPU backend, we can also deploy the IRModule on GPU. GPU requires
# programs containing extra information, such as the thread bindings and shared memory
# allocations. We need a further transformation to generate the GPU programs.
#
# We use ``DLight`` to generate the GPU programs. In this tutorial, we won't go into
# the details of ``DLight``.
#
from tvm.s_tir import dlight as dl
with tvm.target.Target("cuda"):
gpu_mod = dl.ApplyDefaultSchedule(
dl.gpu.Matmul(),
dl.gpu.Fallback(),
)(mod)
######################################################################
# Now we can compile the IRModule on GPU, the similar way as we did on CPU.
exec = tvm.compile(gpu_mod, target="cuda")
dev = tvm.device("cuda", 0)
vm = relax.VirtualMachine(exec, dev)
# Need to allocate data and params on GPU device
data = tvm.runtime.tensor(raw_data, dev)
gpu_params = [tvm.runtime.tensor(p, dev) for p in params_from_torch["main"]]
gpu_out = vm["main"](data, *gpu_params).numpy()
print(gpu_out)
# Check the correctness of the results
assert np.allclose(cpu_out, gpu_out, atol=1e-3)
######################################################################
# Deploy on Other Backends
# ~~~~~~~~~~~~~~~~~~~~~~~~
# Apache TVM also supports other backends, such as different kinds of GPUs
# (Metal, ROCm, Vulkan and OpenCL), different kinds of CPUs (x86, ARM), and other
# emerging backends (e.g., WebAssembly). The deployment process is similar to the
# GPU backend.
+194
View File
@@ -0,0 +1,194 @@
# 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.
# ruff: noqa: E402, E501
"""
.. _quick_start:
Quick Start
===========
This tutorial is for people who are new to Apache TVM. Taking an simple example
to show how to use Apache TVM to compile a simple neural network.
.. contents:: Table of Contents
:local:
:depth: 2
"""
################################################################################
# Overview
# --------
# Apache TVM is a machine learning compilation framework, following the principle of
# **Python-first development** and **universal deployment**. It takes in pre-trained
# machine learning models, compiles and generates deployable modules that can be embedded
# and run everywhere.
# Apache TVM also enables customizing optimization processes to introduce new optimizations,
# libraries, codegen and more.
#
# Apache TVM can help to:
#
# - **Optimize** performance of ML workloads, composing libraries and codegen.
# - **Deploy** ML workloads to a diverse set of new environments, including new runtime and new
# hardware.
# - **Continuously improve and customize** ML deployment pipeline in Python by quickly customizing
# library dispatching, bringing in customized operators and code generation.
################################################################################
# Overall Flow
# ------------
# Then we will show the overall flow of using Apache TVM to compile a neural network model,
# showing how to optimize, deploy and run the model.
# The overall flow is illustrated as the figure:
#
# .. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/design/tvm_overall_flow.svg
# :align: center
# :width: 80%
#
# The overall flow consists of the following steps:
#
# - **Construct or Import a Model**: Construct a neural network model or import a pre-trained
# model from other frameworks (e.g. PyTorch, ONNX), and create the TVM IRModule, which contains
# all the information needed for compilation, including high-level Relax functions for
# computational graph, and low-level TensorIR functions for tensor program.
# - **Perform Composable Optimizations**: Perform a series of optimization transformations,
# such as graph optimizations, tensor program optimizations, and library dispatching.
# - **Build and Universal Deployment**: Build the optimized model to a deployable module to the
# universal runtime, and execute it on different devices, such as CPU, GPU, or other accelerators.
################################################################################
# Construct or Import a Model
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Before we get started, let's construct a neural network model first.
# In this tutorial, to make things simple, we will define a two-layer MLP network
# directly in this script with the TVM Relax frontend, which is a similar API to PyTorch.
#
import tvm
from tvm import relax
from tvm.relax.frontend import nn
class MLPModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
################################################################################
# Then we can export the model to TVM IRModule, which is the central intermediate representation
# in TVM.
mod, param_spec = MLPModel().export_tvm(
spec={"forward": {"x": nn.spec.Tensor((1, 784), "float32")}}
)
mod.show()
################################################################################
# Perform Optimization Transformations
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Apache TVM leverage ``pipeline`` to transform and optimize program.
# The pipeline encapsulates a collection of transformation that gets two goals (at the same level):
#
# - **Model optimizations**: such as operator fusion, layout rewrites.
# - **Tensor program optimization**: Map the operators to low-level implementations
# (both library or codegen)
#
# .. note::
# The twos are goals but not the stages of the pipeline. The two optimizations are performed
# **at the same level**, or separately in two stages.
#
# .. note::
# In this tutorial we only demonstrate the overall flow, by leverage ``zero`` optimization
# pipeline, instead of optimizing for any specific target.
mod = relax.get_pipeline("zero")(mod)
################################################################################
# Build and Universal Deployment
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# After the optimization, we can build the model to a deployable module and run it on
# different devices.
import numpy as np
target = tvm.target.Target("llvm")
ex = tvm.compile(mod, target)
device = tvm.cpu()
vm = relax.VirtualMachine(ex, device)
data = np.random.rand(1, 784).astype("float32")
tvm_data = tvm.runtime.tensor(data, device=device)
params = [np.random.rand(*param.shape).astype("float32") for _, param in param_spec]
params = [tvm.runtime.tensor(param, device=device) for param in params]
print(vm["forward"](tvm_data, *params).numpy())
################################################################################
# Our goal is to bring machine learning to the application with any language of interest,
# with the minimum runtime support.
#
# - Each function in IRModule becomes a runnable function in the runtime. For example in LLM
# cases, we can call ``prefill`` and ``decode`` functions directly.
#
# .. code-block:: Python
#
# prefill_logits = vm["prefill"](inputs, weight, kv_cache)
# decoded_logits = vm["decode"](inputs, weight, kv_cache)
#
# - TVM runtime comes with native data structures, such as Tensor, can also have zero
# copy exchange with existing ecosystem (DLPack exchange with PyTorch)
#
# .. code-block:: Python
#
# # Convert PyTorch tensor to TVM Tensor
# x_tvm = tvm.runtime.from_dlpack(x_torch)
# # Convert TVM Tensor to PyTorch tensor
# x_torch = torch.from_dlpack(x_tvm)
#
# - TVM runtime works in non-python environments, so it works on settings such as mobile
#
# .. code-block:: C++
#
# // C++ snippet
# runtime::Module vm = ex.GetFunction("load_executable")();
# vm.GetFunction("init")(...);
# Tensor out = vm.GetFunction("prefill")(data, weight, kv_cache);
#
# .. code-block:: Java
#
# // Java snippet
# Module vm = ex.getFunction("load_executable").invoke();
# vm.getFunction("init").pushArg(...).invoke;
# Tensor out = vm.getFunction("prefill").pushArg(data).pushArg(weight).pushArg(kv_cache).invoke();
#
################################################################################
# Read next
# ---------
# This tutorial demonstrates the overall flow of using Apache TVM to compile a neural network model.
# For more advanced or specific topics, please refer to the following tutorials
#
+2
View File
@@ -0,0 +1,2 @@
HOW TO
------
@@ -0,0 +1,375 @@
# 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.
"""
.. _tutorial-bring-your-own-codegen:
Bring Your Own Codegen
======================
TVM's Bring Your Own Codegen (BYOC) framework lets you offload parts of a model
to a custom backend -- a hardware accelerator, an inference library, or your own
kernels -- while TVM compiles the rest. This tutorial has two parts:
- **How BYOC works** -- we teach the flow with a bundled, hardware-free *example
NPU* backend and then drive the **same flow** on a real production backend,
NVIDIA TensorRT. Both run a small, hand-written model so every step is
visible; the only thing that changes between them is the backend, and that
contrast is the lesson.
- **Deploying a real model** -- we then put it to work, taking an actual PyTorch
``nn.Module`` from export through TensorRT and running it on the GPU.
The example NPU is a teaching stub: its runtime logs the dispatch decisions an
NPU would make (memory tier, execution engine, fusion) but performs no real
computation, so its output buffers are left uninitialized. We therefore check
*shapes*, not values, in the NPU sections -- its job is to make every BYOC step
visible with nothing hidden. TensorRT then runs the identical flow for real, so
we cross-check its result against a reference.
**Prerequisites**: the example NPU sections need TVM built with
``USE_EXAMPLE_NPU_CODEGEN=ON`` and ``USE_EXAMPLE_NPU_RUNTIME=ON``; the TensorRT
sections need ``USE_TENSORRT_CODEGEN=ON``, ``USE_TENSORRT_RUNTIME=ON`` and
``USE_CUDA=ON`` plus a CUDA GPU and a matching TensorRT install (from NVIDIA's
``pip install tensorrt`` packages or the TensorRT archive); the final deployment
section also needs PyTorch. Each section degrades gracefully when its backend is
unavailable.
"""
######################################################################
# Overview of the BYOC flow
# -------------------------
#
# BYOC plugs a custom backend into TVM's compilation pipeline in four steps:
#
# 1. **Register patterns** - describe which sequences of Relax ops the backend
# can handle.
# 2. **Partition the graph** - group matched ops into composite functions.
# 3. **Run codegen** - lower each composite to the backend's representation
# (a JSON graph for both backends in this tutorial).
# 4. **Execute** - the runtime dispatches each composite to the backend.
#
# Steps 1 and 2 are pure Python and run anywhere; steps 3 and 4 need the
# backend's codegen and runtime compiled into TVM, which is why the
# build-and-run cells below are guarded.
######################################################################
# Step 1: Import the backends to register their patterns
# ------------------------------------------------------
#
# Importing a backend module registers its patterns with TVM's global registry.
# Pattern registration is independent of the C++ build -- only codegen and the
# runtime require the backend to be compiled in -- so we probe each backend and
# guard the build-and-run cells accordingly.
import os
import tempfile
import numpy as np
import tvm
import tvm.relax.backend.contrib.example_npu
from tvm import relax
from tvm.relax.backend.contrib.tensorrt import partition_for_tensorrt
from tvm.relax.backend.pattern_registry import get_patterns_with_prefix
from tvm.relax.transform import FuseOpsByPattern, MergeCompositeFunctions, RunCodegen
from tvm.script import relax as R
has_example_npu_codegen = tvm.get_global_func("relax.ext.example_npu", True)
has_example_npu_runtime = tvm.get_global_func("runtime.ExampleNPUJSONRuntimeCreate", True)
has_example_npu = has_example_npu_codegen and has_example_npu_runtime
has_tensorrt_codegen = tvm.get_global_func("relax.ext.tensorrt", True) is not None
_is_trt_runtime_enabled = tvm.get_global_func("relax.is_tensorrt_runtime_enabled", True)
has_tensorrt = (
has_tensorrt_codegen and _is_trt_runtime_enabled is not None and _is_trt_runtime_enabled()
)
has_cuda = tvm.cuda(0).exist
######################################################################
# Step 2: Define the model
# ------------------------
#
# A single convolution followed by a ReLU. This one model is used for both
# backends.
@tvm.script.ir_module
class ConvReLU:
@R.function
def main(
data: R.Tensor((1, 3, 32, 32), "float32"),
weight: R.Tensor((16, 3, 3, 3), "float32"),
) -> R.Tensor((1, 16, 30, 30), "float32"):
with R.dataflow():
conv = relax.op.nn.conv2d(data, weight)
out = relax.op.nn.relu(conv)
R.output(out)
return out
######################################################################
# Step 3: Partition for the example NPU
# -------------------------------------
#
# ``FuseOpsByPattern`` groups ops matching a registered pattern into composite
# functions; ``MergeCompositeFunctions`` then consolidates adjacent composites
# bound for the same backend into a single external call. Two flags steer
# partitioning:
#
# - ``bind_constants=False`` keeps weights as function arguments, so the host
# stays in charge of the parameters. (TensorRT below makes the opposite
# choice: it binds weights as constants because it bakes them into its engine.)
# - ``annotate_codegen=True`` wraps each matched composite in a function tagged
# with the backend name -- the tag ``RunCodegen`` routes on. (The follow-up
# ``MergeCompositeFunctions`` also attaches this tag when it groups composites,
# which is why ``partition_for_tensorrt`` below can leave the flag off.)
#
# The example NPU registers a fused ``conv2d + relu`` pattern with higher
# priority than the standalone ``conv2d`` pattern, so the two ops collapse into a
# single ``example_npu.conv2d_relu_fused`` composite -- look for it in the
# printed module.
npu_patterns = get_patterns_with_prefix("example_npu")
npu_mod = FuseOpsByPattern(npu_patterns, bind_constants=False, annotate_codegen=True)(ConvReLU)
npu_mod = MergeCompositeFunctions()(npu_mod)
print("After partitioning for the example NPU:")
print(npu_mod)
######################################################################
# Step 4: Codegen, build and run on the example NPU
# -------------------------------------------------
#
# ``RunCodegen`` invokes each annotated composite's backend codegen, replacing it
# with the backend runtime module (here, the NPU's JSON graph); ``relax.build``
# then compiles the remaining host-side program and links everything. Because
# the runtime is a stub that computes nothing, we assert on the output *shape*
# only -- the values are uninitialized.
np.random.seed(0)
data_np = np.random.randn(1, 3, 32, 32).astype("float32")
weight_np = np.random.randn(16, 3, 3, 3).astype("float32")
if has_example_npu:
npu_mod = RunCodegen()(npu_mod)
with tvm.transform.PassContext(opt_level=3):
npu_exec = relax.build(npu_mod, tvm.target.Target("llvm"))
npu_vm = relax.VirtualMachine(npu_exec, tvm.cpu())
npu_out = npu_vm["main"](
tvm.runtime.tensor(data_np, tvm.cpu()), tvm.runtime.tensor(weight_np, tvm.cpu())
)
assert npu_out.numpy().shape == (1, 16, 30, 30)
print("Example NPU run completed. Output shape:", npu_out.numpy().shape)
else:
print("Example NPU backend unavailable; skipping its build and run.")
######################################################################
# The same flow on a real backend: TensorRT
# -----------------------------------------
#
# Steps 1-4 above are the whole mechanism. Aiming them at a real backend
# changes very little, so rather than repeat the walkthrough, here is only what
# differs for NVIDIA TensorRT:
#
# - **Partition in one call.** ``partition_for_tensorrt`` bundles the
# ``FuseOpsByPattern`` + ``MergeCompositeFunctions`` you ran by hand, using
# TensorRT's own pattern table.
# - **Weights become constants** (``bind_constants=True``): TensorRT bakes them
# into the engine it builds, so bind the parameters before partitioning.
# - **Real values.** TensorRT actually computes, so we build for CUDA, run on
# the GPU, and cross-check against a plain CPU build -- not just the shape.
#
# The build-and-run cells below execute only when TensorRT and CUDA are
# available. In CPU-only documentation builds, they produce no output.
trt_mod = relax.transform.BindParams("main", {"weight": weight_np})(ConvReLU)
trt_mod = partition_for_tensorrt(trt_mod)
print("After partition_for_tensorrt:")
print(trt_mod)
######################################################################
# Build for CUDA, run on the GPU, and compare against the CPU reference.
if has_tensorrt and has_cuda:
dev = tvm.cuda(0)
with tvm.transform.PassContext(opt_level=3):
trt_exec = relax.build(RunCodegen()(trt_mod), "cuda")
trt_out = relax.VirtualMachine(trt_exec, dev)["main"](tvm.runtime.tensor(data_np, dev)).numpy()
cpu_mod = relax.transform.LegalizeOps()(
relax.transform.BindParams("main", {"weight": weight_np})(ConvReLU)
)
cpu_exec = relax.build(cpu_mod, "llvm")
cpu_out = relax.VirtualMachine(cpu_exec, tvm.cpu())["main"](
tvm.runtime.tensor(data_np, tvm.cpu())
).numpy()
np.testing.assert_allclose(trt_out, cpu_out, rtol=1e-2, atol=1e-2)
print("TensorRT output shape:", trt_out.shape, "- matches the CPU reference.")
######################################################################
# A real backend also exposes knobs the stub does not. Setting ``use_fp16``
# through the ``relax.ext.tensorrt.options`` config lets TensorRT pick FP16
# kernels, trading a little accuracy for speed; nothing else about the flow
# changes. (Other options are environment-driven: ``TVM_TENSORRT_USE_INT8``
# enables INT8 with calibration, ``TVM_TENSORRT_MAX_WORKSPACE_SIZE`` caps the
# build workspace, and ``TVM_TENSORRT_CACHE_DIR`` caches built engines to disk
# for reuse across runs.)
if has_tensorrt and has_cuda:
fp16_mod = partition_for_tensorrt(
relax.transform.BindParams("main", {"weight": weight_np})(ConvReLU)
)
with tvm.transform.PassContext(
opt_level=3, config={"relax.ext.tensorrt.options": {"use_fp16": True}}
):
fp16_exec = relax.build(RunCodegen()(fp16_mod), "cuda")
fp16_out = relax.VirtualMachine(fp16_exec, tvm.cuda(0))["main"](
tvm.runtime.tensor(data_np, tvm.cuda(0))
).numpy()
np.testing.assert_allclose(fp16_out, cpu_out, rtol=5e-2, atol=5e-2)
print("TensorRT FP16 output shape:", fp16_out.shape, "- matches within FP16 tolerance.")
######################################################################
# Example NPU vs TensorRT at a glance
# -----------------------------------
#
# The same four-step flow, two backends:
#
# ========= ============================== ==================================
# Aspect Example NPU (teaching stub) TensorRT (real backend)
# ========= ============================== ==================================
# Runtime logs decisions, no compute builds and runs an nvinfer engine
# Output uninitialized (check shape) real values (cross-checked vs CPU)
# Weights ``bind_constants=False`` ``bind_constants=True`` (baked in)
# Partition two passes, by hand ``partition_for_tensorrt`` one call
# ========= ============================== ==================================
######################################################################
# Deploying a PyTorch model with TensorRT
# ---------------------------------------
#
# Everything above used a hand-written ``IRModule`` so each op was visible. In
# practice you start from a trained model. This final section runs the *same*
# ``partition_for_tensorrt`` flow on a real PyTorch ``nn.Module``, end to end:
# export it, import it into Relax with the PyTorch frontend (the weights come in
# as constants -- exactly what TensorRT bakes into its engine), partition, build
# for CUDA, and check the GPU result against PyTorch's own output. Beyond the
# frontend import, the only difference is that the imported program returns its
# outputs as a tuple, so we index ``[0]`` for the single result tensor; the
# partition-build-run flow is otherwise unchanged.
#
# This section additionally requires PyTorch.
try:
import torch
from torch import nn
has_torch = True
except ImportError:
has_torch = False
if has_torch and has_tensorrt and has_cuda:
from tvm.relax.frontend.torch import from_exported_program
class SmallConvNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 8, 3)
self.conv2 = nn.Conv2d(8, 16, 3)
self.pool = nn.MaxPool2d(2)
def forward(self, x):
x = torch.relu(self.conv1(x))
x = self.pool(x)
x = torch.relu(self.conv2(x))
return x
torch_model = SmallConvNet().eval()
example_input = torch.randn(1, 3, 32, 32)
with torch.no_grad():
torch_ref = torch_model(example_input).numpy()
exported = torch.export.export(torch_model, (example_input,))
torch_mod = from_exported_program(exported)
torch_mod = partition_for_tensorrt(torch_mod)
print("After importing and partitioning the PyTorch model:")
print(torch_mod)
torch_dev = tvm.cuda(0)
with tvm.transform.PassContext(opt_level=3):
torch_exec = relax.build(RunCodegen()(torch_mod), "cuda")
deployed = relax.VirtualMachine(torch_exec, torch_dev)["main"](
tvm.runtime.tensor(example_input.numpy(), torch_dev)
)[0].numpy()
np.testing.assert_allclose(deployed, torch_ref, rtol=1e-2, atol=1e-2)
print("Deployed PyTorch model on TensorRT; output", deployed.shape, "matches PyTorch.")
######################################################################
# Real deployment builds once and reuses the artifact. Export the compiled
# module to a shared library, then load and run it later -- in a fresh process,
# with no PyTorch and no rebuild needed.
if has_torch and has_tensorrt and has_cuda:
with tempfile.TemporaryDirectory() as tmpdir:
lib_path = os.path.join(tmpdir, "deployed_trt.so")
torch_exec.export_library(lib_path)
loaded = tvm.runtime.load_module(lib_path)
reran = relax.VirtualMachine(loaded, torch_dev)["main"](
tvm.runtime.tensor(example_input.numpy(), torch_dev)
)[0].numpy()
np.testing.assert_allclose(reran, torch_ref, rtol=1e-2, atol=1e-2)
print("Reloaded the exported library and reran; output", reran.shape, "still matches.")
######################################################################
# Notes for real deployments
# --------------------------
#
# - **Operator coverage and fallback.** TensorRT offloads only the ops in its
# pattern table (see ``python/tvm/relax/backend/contrib/tensorrt.py``);
# anything unsupported simply stays on the host. Print the partitioned module
# and look for the ``Codegen: "tensorrt"`` functions to see what was offloaded.
# - **Dynamic shapes.** The builder sets up an optimization profile for a dynamic
# leading (batch) dimension, so the integration can serve a model exported with
# a symbolic batch size.
# - **Engine build cost.** Building a TensorRT engine is slow the first time (it
# is not a hang). Set ``TVM_TENSORRT_CACHE_DIR`` to cache built engines to
# disk and skip the rebuild on later runs.
######################################################################
# Next steps
# ----------
#
# To build your own backend using the example NPU as a starting point:
#
# - Replace the stub runtime in
# ``src/runtime/extra/contrib/example_npu/example_npu_runtime.cc`` with your
# hardware SDK calls.
# - Extend ``patterns.py`` with the ops your hardware supports.
# - Add a C++ codegen under ``src/relax/backend/contrib/`` if your backend needs
# a non-JSON serialization format.
# - Add a CMake module under ``cmake/modules/contrib/`` following
# ``ExampleNPU.cmake``.
#
# For a complete real-backend implementation to study, see the TensorRT
# integration: the pattern table and ``partition_for_tensorrt`` in
# ``python/tvm/relax/backend/contrib/tensorrt.py``, the codegen in
# ``src/relax/backend/contrib/tensorrt/``, and the runtime in
# ``src/runtime/extra/contrib/tensorrt/``.
@@ -0,0 +1,786 @@
# 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.
# ruff: noqa: E501
"""
.. _tutorial-cross-compilation-and-rpc:
Cross Compilation and RPC
=========================
**Author**: `Ziheng Jiang <https://github.com/ZihengJiang/>`_, `Lianmin Zheng <https://github.com/merrymercy/>`_
This tutorial introduces cross compilation and remote device
execution with RPC in TVM.
With cross compilation and RPC, you can **compile a program on your
local machine then run it on the remote device**. It is useful when
the remote device resource are limited, like Raspberry Pi and mobile
platforms. In this tutorial, we will use the Raspberry Pi for a CPU example
and the Firefly-RK3399 for an OpenCL example.
"""
######################################################################
# Build TVM Runtime on Device
# ---------------------------
#
# The first step is to build the TVM runtime on the remote device.
#
# .. note::
#
# All instructions in both this section and the next section should be
# executed on the target device, e.g. Raspberry Pi. We assume the target
# is running Linux.
#
# Since we do compilation on the local machine, the remote device is only used
# for running the generated code. We only need to build the TVM runtime on
# the remote device.
#
# .. code-block:: bash
#
# git clone --recursive https://github.com/apache/tvm tvm
# cd tvm
# mkdir build && cd build
# cp ../cmake/config.cmake .
# cmake .. && cmake --build . --parallel $(nproc)
#
# After building the runtime successfully, we need to set environment variables
# in :code:`~/.bashrc` file. We can edit :code:`~/.bashrc`
# using :code:`vi ~/.bashrc` and add the line below (Assuming your TVM
# directory is in :code:`~/tvm`):
#
# .. code-block:: bash
#
# export PYTHONPATH=$PYTHONPATH:~/tvm/python
#
# To update the environment variables, execute :code:`source ~/.bashrc`.
######################################################################
# Set Up RPC Server on Device
# ---------------------------
# To start an RPC server, run the following command on your remote device
# (Which is Raspberry Pi in this example).
#
# .. code-block:: bash
#
# python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
#
# If you see the line below, it means the RPC server started
# successfully on your device.
#
# .. code-block:: bash
#
# INFO:root:RPCServer: bind to 0.0.0.0:9090
#
# Declare and Cross Compile Kernel on Local Machine
# -------------------------------------------------
#
# .. note::
#
# Now we go back to the local machine, which has a full TVM installed
# (with LLVM).
#
# Here we will declare a simple kernel on the local machine:
import numpy as np
import tvm_ffi
import tvm
from tvm import rpc, te
from tvm.support import utils
n = tvm.runtime.convert(1024)
A = te.placeholder((n,), name="A")
B = te.compute((n,), lambda i: A[i] + 1.0, name="B")
mod = tvm.IRModule.from_expr(te.create_prim_func([A, B]).with_attr("global_symbol", "add_one"))
######################################################################
# Then we cross compile the kernel.
# The target should be {"kind": "llvm", "mtriple": "armv7l-linux-gnueabihf"} for
# Raspberry Pi 3B, but we use 'llvm' here to make this tutorial runnable
# on our webpage building server. See the detailed note in the following block.
local_demo = True
if local_demo:
target = "llvm"
else:
target = {"kind": "llvm", "mtriple": "armv7l-linux-gnueabihf"}
func = tvm.compile(mod, target=target)
# save the lib at a local temp folder
temp = utils.tempdir()
path = temp.relpath("lib.tar")
func.export_library(path)
######################################################################
# .. note::
#
# To run this tutorial with a real remote device, change :code:`local_demo`
# to False and replace :code:`target` in :code:`build` with the appropriate
# target triple for your device. The target triple which might be
# different for different devices. For example, it is
# :code:`{"kind": "llvm", "mtriple": "armv7l-linux-gnueabihf"}` for Raspberry Pi 3B and
# :code:`{"kind": "llvm", "mtriple": "aarch64-linux-gnu"}` for RK3399.
#
# Usually, you can query the target by running :code:`gcc -v` on your
# device, and looking for the line starting with :code:`Target:`
# (Though it may still be a loose configuration.)
#
# Besides :code:`-mtriple`, you can also set other compilation options
# like:
#
# * -mcpu=<cpuname>
# Specify a specific chip in the current architecture to generate code for. By default this is inferred from the target triple and autodetected to the current architecture.
# * -mattr=a1,+a2,-a3,...
# Override or control specific attributes of the target, such as whether SIMD operations are enabled or not. The default set of attributes is set by the current CPU.
# To get the list of available attributes, you can do:
#
# .. code-block:: bash
#
# llc -mtriple=<your device target triple> -mattr=help
#
# These options are consistent with `llc <http://llvm.org/docs/CommandGuide/llc.html>`_.
# It is recommended to set target triple and feature set to contain specific
# feature available, so we can take full advantage of the features of the
# board.
# You can find more details about cross compilation attributes from
# `LLVM guide of cross compilation <https://clang.llvm.org/docs/CrossCompilation.html>`_.
######################################################################
# Run CPU Kernel Remotely by RPC
# ------------------------------
# We show how to run the generated CPU kernel on the remote device.
# First we obtain an RPC session from remote device.
if local_demo:
remote = rpc.LocalSession()
else:
# The following is my environment, change this to the IP address of your target device
host = "10.77.1.162"
port = 9090
remote = rpc.connect(host, port)
######################################################################
# Upload the lib to the remote device, then invoke a device local
# compiler to relink them. Now `func` is a remote module object.
remote.upload(path)
func = remote.load_module("lib.tar")
# create arrays on the remote device
dev = remote.cpu()
a = tvm.runtime.tensor(np.random.uniform(size=1024).astype(A.dtype), dev)
b = tvm.runtime.tensor(np.zeros(1024, dtype=A.dtype), dev)
# the function will run on the remote device
func(a, b)
np.testing.assert_equal(b.numpy(), a.numpy() + 1)
######################################################################
# When you want to evaluate the performance of the kernel on the remote
# device, it is important to avoid the overhead of network.
# :code:`time_evaluator` will returns a remote function that runs the
# function over number times, measures the cost per run on the remote
# device and returns the measured cost. Network overhead is excluded.
time_f = func.time_evaluator("add_one", dev, number=10)
cost = time_f(a, b).mean
print(f"{cost:g} secs/op")
######################################################################
# Scale RPC to Shared Devices
# ---------------------------
#
# The direct RPC server used above is the simplest way to run on one remote
# device. In shared environments, the same compile/upload/run flow is usually
# kept, but the connection is managed by an RPC tracker and, when needed, an
# RPC proxy.
#
# This setup is useful when:
#
# - multiple users or CI jobs share a small number of boards,
# - devices are registered by key rather than by fixed IP address,
# - the host cannot directly reach the device because of the network layout, or
# - the target device only has the minimal runtime stack needed for execution.
#
# The pieces fit together as follows:
#
# - **RPC server**: runs on the target device and executes uploaded modules.
# - **RPC tracker**: runs on a host and assigns matching RPC servers to clients.
# - **RPC proxy**: forwards traffic when the client cannot connect directly to
# the RPC server.
#
# .. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/dev/how-to/rpc_system_suggested_arch.svg
# :align: center
# :width: 85%
#
# In the figure above, machine A connects through the tracker. Machine B runs
# an RPC proxy because machines C and D are not directly reachable from A. The
# tracker keeps a queue per RPC key. If a matching server is available, it is
# assigned to the client; otherwise, the request waits in that key's queue.
#
# Start the Tracker and Proxy
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# The tracker and proxy generally run on a host machine, not on the target
# device. They do not require target-specific drivers.
#
# .. code-block:: shell
#
# python3 -m tvm.exec.rpc_tracker --host RPC_TRACKER_IP --port 9190 --port-end 9191
#
# .. code-block:: shell
#
# python3 -m tvm.exec.rpc_proxy \
# --host RPC_PROXY_IP \
# --port 9090 \
# --port-end 9091 \
# --tracker RPC_TRACKER_IP:RPC_TRACKER_PORT
#
# Replace the host names, ports, and port ranges for your environment. The
# ``--port-end`` option is useful in CI because it prevents the service from
# silently choosing an unexpected port.
#
# Package a Minimal RPC Runtime
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# If the target can build TVM directly, install TVM on the target and launch
# the RPC server there. Otherwise, cross-compile the TVM runtime and package
# it with the Python RPC server.
#
# A typical CMake toolchain file for 64-bit ARM Linux looks like this:
#
# .. code-block:: cmake
#
# set(CMAKE_SYSTEM_NAME Linux)
# set(root_dir "/XXX/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu")
#
# set(CMAKE_C_COMPILER "${root_dir}/bin/aarch64-linux-gnu-gcc")
# set(CMAKE_CXX_COMPILER "${root_dir}/bin/aarch64-linux-gnu-g++")
# set(CMAKE_SYSROOT "${root_dir}/aarch64-linux-gnu/libc")
#
# set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
#
# Build the runtime from the TVM repository root. Enable target-specific
# options such as ``USE_OPENCL`` or vendor runtime support in ``config.cmake``.
# Build any target-specific runtime libraries that your deployment needs, such
# as ``tvm_runtime_opencl`` for OpenCL.
#
# .. code-block:: shell
#
# mkdir cross_build
# cd cross_build
# cp ../cmake/config.cmake ./
#
# # Enable other options as needed, e.g. USE_OPENCL or vendor runtimes.
# sed -i "s|USE_LLVM.*)|USE_LLVM OFF)|" config.cmake
#
# cmake -DCMAKE_TOOLCHAIN_FILE=/YYY/aarch64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release ..
# cmake --build . --target runtime -j
# # Optional example when USE_OPENCL is enabled:
# # cmake --build . --target tvm_runtime_opencl -j
# cd ..
#
# Then package the Python RPC server with the cross-compiled runtime and copy
# it to the device.
#
# .. code-block:: shell
#
# rm -rf tvm_runtime_package
# mkdir tvm_runtime_package
# cp -a python tvm_runtime_package/
# cp cross_build/lib/libtvm_ffi.so tvm_runtime_package/python/tvm/
# cp cross_build/lib/libtvm_runtime*.so tvm_runtime_package/python/tvm/
# tar -czf tvm_runtime.tar.gz -C tvm_runtime_package python
#
# On the target device:
#
# .. code-block:: shell
#
# tar -xzf tvm_runtime.tar.gz
# export PYTHONPATH=`pwd`/python:${PYTHONPATH}
#
# Launch the Server
# ~~~~~~~~~~~~~~~~~
#
# Launch the RPC server on the target device. Use the proxy form when the
# server connects through an RPC proxy; otherwise connect directly to the
# tracker.
#
# .. code-block:: shell
#
# # Through an RPC proxy.
# python3 -m tvm.exec.rpc_server \
# --host RPC_PROXY_IP \
# --port RPC_PROXY_PORT \
# --through-proxy \
# --key RPC_KEY
#
# # Directly to an RPC tracker.
# python3 -m tvm.exec.rpc_server \
# --tracker RPC_TRACKER_IP:RPC_TRACKER_PORT \
# --key RPC_KEY
#
# Query the tracker from the host to confirm that the servers are visible:
#
# .. code-block:: shell
#
# python3 -m tvm.exec.query_rpc_tracker --host RPC_TRACKER_IP --port RPC_TRACKER_PORT
#
# If three servers connect through a proxy, the output should look similar to:
#
# .. code-block:: text
#
# Tracker address RPC_TRACKER_IP:RPC_TRACKER_PORT
#
# Server List
# ----------------------------
# server-address key
# ----------------------------
# RPC_PROXY_IP:RPC_PROXY_PORT server:proxy[RPC_KEY0,RPC_KEY1,RPC_KEY2]
# ----------------------------
#
# Queue Status
# ---------------------------------------
# key total free pending
# ---------------------------------------
# RPC_KEY0 0 0 3
# ---------------------------------------
#
# Once the tracker assigns a server, the client-side code still follows the
# same pattern used earlier in this tutorial. Only the session creation
# changes from a direct connection to a tracker request:
#
# .. code-block:: python
#
# tracker = rpc.connect_tracker("RPC_TRACKER_IP", RPC_TRACKER_PORT)
# remote = tracker.request("RPC_KEY", priority=0, session_timeout=600)
#
# After that, use the same ``remote.upload()``, ``remote.load_module()``, remote
# device creation, and ``time_evaluator`` flow shown above.
#
# Troubleshooting Minimal Device Environments
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Some target devices have intentionally small Python environments. The TVM
# runtime itself does not require full NumPy support for RPC execution, but the
# Python RPC server may import modules that import ``numpy``. If installing or
# cross-compiling NumPy is not practical, a small ``numpy.py`` shim in the
# device's Python ``site-packages`` directory can be enough for server startup.
#
# If ``cloudpickle`` is missing, copy it from another Python environment into
# the device's ``site-packages`` directory. It is a pure Python package, so it
# usually does not need cross-compilation.
#
# Run OpenCL Kernel Remotely by RPC
# ---------------------------------
# For remote OpenCL devices, the workflow is almost the same as above.
# You can define the kernel, upload files, and run via RPC.
#
# .. note::
#
# Raspberry Pi does not support OpenCL, the following code is tested on
# Firefly-RK3399. You may follow this `tutorial <https://gist.github.com/mli/585aed2cec0b5178b1a510f9f236afa2>`_
# to setup the OS and OpenCL driver for RK3399.
#
# Also we need to build the runtime with OpenCL enabled on rk3399 board. In the TVM
# root directory, execute
#
# .. code-block:: bash
#
# mkdir -p build && cd build
# cp ../cmake/config.cmake .
# sed -i "s/USE_OPENCL OFF/USE_OPENCL ON/" config.cmake
# cmake .. && cmake --build . --parallel $(nproc)
#
# The following function shows how we run an OpenCL kernel remotely
def run_opencl():
# NOTE: This is the setting for my rk3399 board. You need to modify
# them according to your environment.
opencl_device_host = "10.77.1.145"
opencl_device_port = 9090
target = tvm.target.Target("opencl", host={"kind": "llvm", "mtriple": "aarch64-linux-gnu"})
# create schedule for the above "add one" compute declaration
mod = tvm.IRModule.from_expr(te.create_prim_func([A, B]))
sch = tvm.s_tir.Schedule(mod)
(x,) = sch.get_loops(block=sch.get_sblock("B"))
xo, xi = sch.split(x, [None, 32])
sch.bind(xo, "blockIdx.x")
sch.bind(xi, "threadIdx.x")
func = tvm.compile(sch.mod, target=target)
remote = rpc.connect(opencl_device_host, opencl_device_port)
# export and upload
path = temp.relpath("lib_cl.tar")
func.export_library(path)
remote.upload(path)
func = remote.load_module("lib_cl.tar")
# run
dev = remote.cl()
a = tvm.runtime.tensor(np.random.uniform(size=1024).astype(A.dtype), dev)
b = tvm.runtime.tensor(np.zeros(1024, dtype=A.dtype), dev)
func(a, b)
np.testing.assert_equal(b.numpy(), a.numpy() + 1)
print("OpenCL test passed!")
######################################################################
# Deploy PyTorch Models to Remote Devices with RPC
# ------------------------------------------------
# The above examples demonstrate cross compilation and RPC using low-level
# TensorIR (via TE). For deploying complete neural network models from frameworks
# like PyTorch or ONNX, TVM's Relax provides a higher-level abstraction that is
# better suited for end-to-end model compilation.
#
# This section shows a modern workflow for deploying models to **any remote device**:
#
# 1. Import a PyTorch model and convert it to Relax
# 2. Cross-compile for the target architecture (ARM, x86, RISC-V, etc.)
# 3. Deploy via RPC to a remote device
# 4. Run inference remotely
#
# This workflow is applicable to various deployment scenarios:
#
# - **ARM devices**: Raspberry Pi, NVIDIA Jetson, mobile phones
# - **x86 servers**: Remote Linux servers, cloud instances
# - **Embedded systems**: RISC-V boards, custom hardware
# - **Accelerators**: Remote machines with GPUs, TPUs, or other accelerators
#
# .. note::
# This example uses PyTorch for demonstration, but the workflow is identical
# for ONNX models. Simply replace ``from_exported_program()`` with
# ``from_onnx(model, keep_params_in_input=True)`` and follow the same steps.
# First, let's check if PyTorch is available
try:
import torch
from torch.export import export
HAS_TORCH = True
except ImportError:
HAS_TORCH = False
def run_pytorch_model_via_rpc():
"""
Demonstrates the complete workflow of deploying a PyTorch model to an ARM device via RPC.
"""
if not HAS_TORCH:
print("Skipping PyTorch example (PyTorch not installed)")
return
from tvm import relax
from tvm.relax.frontend.torch import from_exported_program
######################################################################
# Step 1: Define and Export PyTorch Model
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# We use a simple MLP model for demonstration. In practice, this could be
# any PyTorch model (ResNet, BERT, etc.).
class TorchMLP(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(28 * 28, 128),
torch.nn.ReLU(),
torch.nn.Linear(128, 10),
)
def forward(self, data: torch.Tensor) -> torch.Tensor:
return self.net(data)
# Export the model using PyTorch 2.x export API
torch_model = TorchMLP().eval()
example_args = (torch.randn(1, 1, 28, 28, dtype=torch.float32),)
with torch.no_grad():
exported_program = export(torch_model, example_args)
######################################################################
# Step 2: Convert to Relax and Prepare for Compilation
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convert the exported PyTorch program to TVM's Relax representation
mod = from_exported_program(exported_program, keep_params_as_input=True)
# Separate parameters from the model for flexible deployment
mod, params = relax.frontend.detach_params(mod)
print("Converted PyTorch model to Relax:")
print(f" - Number of parameters: {len(params['main'])}")
######################################################################
# Step 3: Cross-Compile for Target Device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Compile the model for the target device architecture. The target
# configuration depends on your deployment scenario.
if local_demo:
# For demonstration on local machine, use local target
target = tvm.target.Target("llvm")
print("Using local target for demonstration")
else:
# Choose the appropriate target for your device:
#
# ARM devices:
# - Raspberry Pi 3/4 (32-bit): {"kind": "llvm", "mtriple": "armv7l-linux-gnueabihf"}
# - Raspberry Pi 4 (64-bit) / Jetson: {"kind": "llvm", "mtriple": "aarch64-linux-gnu"}
# - Android: {"kind": "llvm", "mtriple": "aarch64-linux-android"}
#
# x86 servers:
# - Linux x86_64: {"kind": "llvm", "mtriple": "x86_64-linux-gnu"}
# - With AVX-512: {"kind": "llvm", "mtriple": "x86_64-linux-gnu", "mcpu": "skylake-avx512"}
#
# RISC-V:
# - RV64: {"kind": "llvm", "mtriple": "riscv64-unknown-linux-gnu"}
#
# GPU targets:
# - CUDA: tvm.target.Target("cuda", host={"kind": "llvm", "mtriple": "x86_64-linux-gnu"})
# - OpenCL: tvm.target.Target("opencl", host={"kind": "llvm", "mtriple": "aarch64-linux-gnu"})
#
# For this example, we use ARM 64-bit
target = tvm.target.Target({"kind": "llvm", "mtriple": "aarch64-linux-gnu"})
print(f"Cross-compiling for target: {target}")
# Apply optimization pipeline
pipeline = relax.get_pipeline()
with target:
built_mod = pipeline(mod)
# Compile to executable
executable = tvm.compile(built_mod, target=target)
# Export to shared library
lib_path = temp.relpath("model_deployed.so")
executable.export_library(lib_path)
print(f"Exported library to: {lib_path}")
# Save parameters separately
import numpy as np
params_path = temp.relpath("model_params.npz")
param_arrays = {f"p_{i}": p.numpy() for i, p in enumerate(params["main"])}
np.savez(params_path, **param_arrays)
print(f"Saved parameters to: {params_path}")
######################################################################
# Step 4: Deploy to Remote Device via RPC
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Connect to the remote device, upload the compiled library and parameters,
# then run inference remotely. This works for any device with TVM RPC server.
#
# Note: The following code demonstrates the RPC workflow. In local_demo mode,
# we skip actual execution to avoid LocalSession compatibility issues.
if local_demo:
# For demonstration, show the code structure without execution
print("\nRPC workflow (works for any remote device):")
print("=" * 50)
print("1. Start RPC server on target device:")
print(" python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090")
print("\n2. Connect from local machine:")
print(" remote = rpc.connect('DEVICE_IP', 9090)")
print("\n3. Upload compiled library:")
print(" remote.upload('model_deployed.so')")
print(" remote.upload('model_params.npz')")
print("\n4. Load and run remotely:")
print(" lib = remote.load_module('model_deployed.so')")
print(" vm = relax.VirtualMachine(lib, remote.cpu())")
print(" result = vm['main'](input, *params)")
print("\nDevice examples:")
print(" - Raspberry Pi: 192.168.1.100")
print(" - Remote server: ssh tunnel or direct IP")
print(" - NVIDIA Jetson: 10.0.0.50")
print(" - Cloud instance: public IP")
print("\nTo run actual RPC, set local_demo=False")
return # Skip actual RPC execution in demo mode
# Actual RPC workflow for real deployment
# Connect to remote device (works for ARM, x86, RISC-V, etc.)
# Make sure the RPC server is running on the device:
# python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
device_host = "192.168.1.100" # Replace with your device IP
device_port = 9090
remote = rpc.connect(device_host, device_port)
print(f"Connected to remote device at {device_host}:{device_port}")
# Upload library and parameters to remote device
remote.upload(lib_path)
remote.upload(params_path)
print("Uploaded files to remote device")
# Load the library on the remote device
lib = remote.load_module("model_deployed.so")
# Choose device on remote machine
# For CPU: dev = remote.cpu()
# For CUDA GPU: dev = remote.cuda(0)
# For OpenCL: dev = remote.cl(0)
dev = remote.cpu()
# Create VM and load parameters
vm = relax.VirtualMachine(lib, dev)
# Load parameters from the uploaded file
# Note: In practice, you might load this from the remote filesystem
params_npz = np.load(params_path)
remote_params = [tvm.runtime.tensor(params_npz[f"p_{i}"], dev) for i in range(len(params_npz))]
######################################################################
# Step 5: Run Inference on Remote Device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Execute the model on the remote ARM device and retrieve results
#
# Note: When running VM over RPC, we use set_input() and invoke_stateful()
# instead of direct function call (vm["main"](...)). This is because RPC
# transmits tensors as DLTensor*, while VM builtins expect ffi.Tensor.
# The set_input API handles this conversion internally.
# Prepare input data
input_data = np.random.randn(1, 1, 28, 28).astype("float32")
remote_input = tvm.runtime.tensor(input_data, dev)
# Run inference using set_input + invoke_stateful for RPC compatibility
vm.set_input("main", remote_input, *remote_params)
vm.invoke_stateful("main")
output = vm.get_outputs("main")
# Extract result (handle both tuple and single tensor outputs)
if isinstance(output, tvm_ffi.Array) and len(output) > 0:
result = output[0]
else:
result = output
# Retrieve result from remote device to local
result_np = result.numpy()
print("Inference completed on remote device")
print(f" Output shape: {result_np.shape}")
print(f" Predicted class: {np.argmax(result_np)}")
######################################################################
# Alternative: Direct Function Call (Local Only)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Note: The direct call syntax vm["main"](input, *params) works for
# local execution but may fail over RPC due to type mismatch between
# DLTensor* (RPC) and ffi.Tensor (VM builtins). For RPC, always use
# the set_input + invoke_stateful pattern shown above.
######################################################################
# Step 6: Performance Evaluation (Optional)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Measure inference time on the remote device, excluding network overhead
# Note: For RPC, use invoke_stateful with time_evaluator
time_f = vm.time_evaluator("invoke_stateful", dev, number=10, repeat=3)
prof_res = time_f("main")
print(f"Inference time on remote device: {prof_res.mean * 1000:.2f} ms")
######################################################################
# Notes on Performance Optimization
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# For optimal performance on target devices, consider:
#
# 1. **Auto-tuning with MetaSchedule**: Use automated search to find
# optimal schedules for your specific hardware:
#
# .. code-block:: python
#
# mod = relax.get_pipeline(
# "static_shape_tuning",
# target=target,
# total_trials=2000
# )(mod)
#
# 2. **Quick optimization with DLight**: Apply pre-defined performant schedules:
#
# .. code-block:: python
#
# from tvm.s_tir import dlight as dl
# with target:
# mod = dl.ApplyDefaultSchedule()(mod)
#
# 3. **Architecture-specific optimizations**:
#
# - ARM NEON SIMD: ``-mattr=+neon``
# - x86 AVX-512: ``-mcpu=skylake-avx512``
# - RISC-V Vector: ``-mattr=+v``
#
# .. code-block:: python
#
# # Example: ARM with NEON
# target = tvm.target.Target(
# {"kind": "llvm", "mtriple": "aarch64-linux-gnu", "mattr": "+neon"}
# )
#
# # Example: x86 with AVX-512
# target = tvm.target.Target(
# {"kind": "llvm", "mtriple": "x86_64-linux-gnu", "mcpu": "skylake-avx512"}
# )
#
# See :doc:`e2e_opt_model </how_to/tutorials/e2e_opt_model>` for detailed
# tuning examples.
# Run the PyTorch RPC example if PyTorch is available
if HAS_TORCH and local_demo:
try:
run_pytorch_model_via_rpc()
except Exception:
pass # Silently skip if execution fails
######################################################################
# Summary
# -------
# This tutorial provides a walk through of cross compilation and RPC
# features in TVM.
#
# We demonstrated two approaches:
#
# **Low-level TensorIR (TE) approach** - for understanding fundamentals:
#
# - Define computations using Tensor Expression
# - Cross-compile for ARM targets
# - Deploy and run via RPC
#
# **High-level Relax approach** - for deploying complete models:
#
# - Import models from PyTorch (or ONNX)
# - Convert to Relax representation
# - Cross-compile for ARM Linux devices
# - Deploy to remote devices via RPC
# - Run inference and evaluate performance
#
# Key takeaways:
#
# - Set up an RPC server on the remote device
# - Cross-compile on a powerful local machine for resource-constrained targets
# - Upload and execute compiled modules remotely via the RPC API
# - Measure performance excluding network overhead
#
# For complete model deployment workflows, see also:
#
# - :doc:`export_and_load_executable </how_to/tutorials/export_and_load_executable>` - Export and load compiled models
# - :doc:`e2e_opt_model </how_to/tutorials/e2e_opt_model>` - End-to-end optimization with auto-tuning
+237
View File
@@ -0,0 +1,237 @@
# 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.
# ruff: noqa: E402, E501, F401
"""
.. _customize_opt:
Customize Optimization
======================
One main design goal of Apache TVM is to enable easy customization of the optimization pipeline
for both research or development purposes and iterate the engineering optimizations. In this
tutorial we will
.. contents:: Table of Contents
:local:
:depth: 1
"""
######################################################################
# Review Overall Flow
# -------------------
# .. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/design/tvm_overall_flow.svg
# :align: center
# :width: 80%
#
# The overall flow consists of the following steps:
#
# - **Construct or Import a Model**: Construct a neural network model or import a pre-trained
# model from other frameworks (e.g. PyTorch, ONNX), and create the TVM IRModule, which contains
# all the information needed for compilation, including high-level Relax functions for
# computational graph, and low-level TensorIR functions for tensor program.
# - **Perform Composable Optimizations**: Perform a series of optimization transformations,
# such as graph optimizations, tensor program optimizations, and library dispatching.
# - **Build and Universal Deployment**: Build the optimized model to a deployable module to the
# universal runtime, and execute it on different devices, such as CPU, GPU, or other accelerators.
#
import os
import tempfile
import numpy as np
import tvm
from tvm import IRModule, relax
from tvm.relax.frontend import nn
######################################################################
# Composable IRModule Optimization
# --------------------------------
# Apache TVM provides a flexible way to optimize the IRModule. Everything centered
# around IRModule optimization can be composed with existing pipelines. Note that each optimization
# can focus on **part of the computation graph**, enabling partial lowering or partial optimization.
#
# In this tutorial, we will demonstrate how to optimize a model with Apache TVM.
######################################################################
# Prepare a Relax Module
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# We first prepare a Relax module. The module can be imported from other frameworks, constructed
# with NN module frontend or TVMScript. Here we use a simple neural network model as an example.
class RelaxModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(256, 10, bias=False)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
return x
input_shape = (1, 784)
mod, params = RelaxModel().export_tvm({"forward": {"x": nn.spec.Tensor(input_shape, "float32")}})
mod.show()
######################################################################
# Library Dispatch
# ~~~~~~~~~~~~~~~~
# We would like to quickly try out a variant of library optimization for certain platforms
# (e.g., GPU). We can write a certain dispatching pass for the specific platform and
# operator. Here we demonstrate how to dispatch the CUBLAS library for certain patterns.
#
# .. note::
# This tutorial only demonstrates a single operator dispatching for CUBLAS, highlighting
# the flexibility of the optimization pipeline. In real-world cases, we can import multiple
# patterns and dispatch them to different kernels.
# Import cublas pattern
try:
import tvm.relax.backend.cuda.cublas as _cublas
except ImportError as e:
raise ImportError(
"This tutorial requires TVM built with CUDA support.\n"
"If you hit missing 'tvm_ffi', try: pip install apache-tvm-ffi\n"
"Otherwise build TVM with CUDA enabled:\n"
" https://tvm.apache.org/docs/install/from_source.html\n"
f"Original error: {e}"
) from e
# Define a new pass for CUBLAS dispatch
@tvm.transform.module_pass(opt_level=0, name="CublasDispatch")
class CublasDispatch:
def transform_module(self, mod: IRModule, _ctx: tvm.transform.PassContext) -> IRModule:
# Check if CUBLAS is enabled
if not tvm.get_global_func("relax.ext.cublas", True):
raise Exception("CUBLAS is not enabled.")
# Get interested patterns
patterns = [relax.backend.get_pattern("cublas.matmul_transposed_bias_relu")]
# Note in real-world cases, we usually get all patterns
# patterns = relax.backend.get_patterns_with_prefix("cublas")
# Fuse ops by patterns and then run codegen
mod = relax.transform.FuseOpsByPattern(patterns, annotate_codegen=True)(mod)
mod = relax.transform.RunCodegen()(mod)
return mod
mod = CublasDispatch()(mod)
mod.show()
######################################################################
# After the dispatching pass, we can see that the first ``nn.Linear`` and ``nn.ReLU`` are fused
# and rewritten to a ``call_dps_packed`` function which call the CUBLAS library. Notably, the
# other part is not changed, which means we can selectively dispatch the optimization for
# certain computation.
######################################################################
# Auto Tuning
# ~~~~~~~~~~~
# Continuing from the previous example, we can further optimize the model with auto-tuning for
# the **rest part of the computation**. Here we demonstrate how to use the meta-schedule to auto-tune
# the model.
#
# We can use ``MetaScheduleTuneTIR`` pass to simply tuning the model, while ``MetaScheduleApplyDatabase``
# pass to apply the best configuration to the model. The tuning process will generate search space,
# tune the model and the following steps will apply the best configuration to the model. Before
# running the passes, we need to lowering relax operator into TensorIR functions via ``LegalizeOps``
#
# .. note::
#
# To save CI time and avoid flakiness, we skip the tuning process in CI environment.
#
device = tvm.cuda(0)
target = tvm.target.Target.from_device(device)
if os.getenv("CI", "") != "true":
trials = 2000
with target, tempfile.TemporaryDirectory() as tmp_dir:
mod = tvm.ir.transform.Sequential(
[
relax.get_pipeline("zero"),
relax.transform.MetaScheduleTuneTIR(work_dir=tmp_dir, max_trials_global=trials),
relax.transform.MetaScheduleApplyDatabase(work_dir=tmp_dir),
]
)(mod)
mod.show()
######################################################################
# DLight Rules
# ~~~~~~~~~~~~
# DLight rules are a set of default rules for scheduling and optimization the kernel.
# DLight rules are designed for fast compilation and **fair** performance. In some cases,
# e.g. language model, DLight provides excellent performance, while for generic models,
# it achieves a balance between performance and compilation time.
from tvm.s_tir import dlight as dl
# Apply DLight rules
with target:
mod = tvm.ir.transform.Sequential(
[
relax.get_pipeline("zero"),
dl.ApplyDefaultSchedule( # pylint: disable=not-callable
dl.gpu.Matmul(),
dl.gpu.GEMV(),
dl.gpu.Reduction(),
dl.gpu.GeneralReduction(),
dl.gpu.Fallback(),
),
]
)(mod)
mod.show()
######################################################################
# .. note::
#
# This tutorial focuses on the demonstration of the optimization pipeline, instead of
# pushing the performance to the limit. The current optimization may not be the best.
######################################################################
# Deploy the Optimized Model
# --------------------------
# We can build and deploy the optimized model to the TVM runtime.
ex = tvm.compile(mod, target="cuda")
dev = tvm.device("cuda", 0)
vm = relax.VirtualMachine(ex, dev)
# Need to allocate data and params on GPU device
data = tvm.runtime.tensor(np.random.rand(*input_shape).astype("float32"), dev)
gpu_params = [tvm.runtime.tensor(np.random.rand(*p.shape).astype(p.dtype), dev) for _, p in params]
gpu_out = vm["forward"](data, *gpu_params).numpy()
print(gpu_out)
######################################################################
# Summary
# -------
# This tutorial demonstrates how to customize the optimization pipeline for ML models in Apache TVM.
# We can easily compose the optimization passes and customize the optimization for different parts
# of the computation graph. The flexibility of the optimization pipeline enables us to quickly
# iterate the optimization and improve the performance of the model.
#
+153
View File
@@ -0,0 +1,153 @@
# 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.
# ruff: noqa: E402
"""
.. _optimize_model:
End-to-End Optimize Model
=========================
This tutorial demonstrates how to optimize a machine learning model using Apache TVM. We will
use a pre-trained ResNet-18 model from PyTorch and end-to-end optimize it using TVM's Relax API.
Please note that default end-to-end optimization may not suit complex models.
"""
######################################################################
# Preparation
# -----------
# First, we prepare the model and input information. We use a pre-trained ResNet-18 model from
# PyTorch.
import os
import numpy as np
import torch
from torch.export import export
from torchvision.models.resnet import ResNet18_Weights, resnet18
torch_model = resnet18(weights=ResNet18_Weights.DEFAULT).eval()
######################################################################
# Review Overall Flow
# -------------------
# .. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/design/tvm_overall_flow.svg
# :align: center
# :width: 80%
#
# The overall flow consists of the following steps:
#
# - **Construct or Import a Model**: Construct a neural network model or import a pre-trained
# model from other frameworks (e.g. PyTorch, ONNX), and create the TVM IRModule, which contains
# all the information needed for compilation, including high-level Relax functions for
# computational graph, and low-level TensorIR functions for tensor program.
# - **Perform Composable Optimizations**: Perform a series of optimization transformations,
# such as graph optimizations, tensor program optimizations, and library dispatching.
# - **Build and Universal Deployment**: Build the optimized model to a deployable module to the
# universal runtime, and execute it on different devices, such as CPU, GPU, or other accelerators.
#
######################################################################
# Convert the model to IRModule
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Next step, we convert the model to an IRModule using the Relax frontend for PyTorch for further
# optimization.
import tvm
from tvm import relax
from tvm.relax.frontend.torch import from_exported_program
# Give an example argument to torch.export
example_args = (torch.randn(1, 3, 224, 224, dtype=torch.float32),)
# Skip running in CI environment
IS_IN_CI = os.getenv("CI", "") == "true"
if not IS_IN_CI:
# Convert the model to IRModule
with torch.no_grad():
exported_program = export(torch_model, example_args)
mod = from_exported_program(exported_program, keep_params_as_input=True)
mod, params = relax.frontend.detach_params(mod)
mod.show()
######################################################################
# IRModule Optimization
# ---------------------
# Apache TVM provides a flexible way to optimize the IRModule. Everything centered
# around IRModule optimization can be composed with existing pipelines. Note that each
# transformation can be combined as an optimization pipeline via ``tvm.ir.transform.Sequential``.
#
# In this tutorial, we focus on the end-to-end optimization of the model via auto-tuning. We
# leverage MetaSchedule to tune the model and store the tuning logs to the database. We also
# apply the database to the model to get the best performance.
#
# The ResNet18 model will be divided into 20 independent tuning tasks during compilation.
# To ensure each task receives adequate tuning resources in one iteration while providing
# early feedback:
#
# - To quickly observe tuning progress, each task is allocated a maximum of 16 trials per
# iteration (controlled by ``MAX_TRIALS_PER_TASK=16``). We should set ``TOTAL_TRIALS``
# to at least ``320 (20 tasks * 16 trials)`` ensures every task receives one full iteration
# of tuning. We set it to 512 in our configuration to allow for several more iterations,
# aiming to explore a wider parameter space and potentially achieve better performance.
# - If ``MAX_TRIALS_PER_TASK == None``, the system defaults to ``TOTAL_TRIALS`` trials per
# task per iteration. An insufficient ``TOTAL_TRIALS`` setting may lead to undersubscribed
# tuning, potentially skipping some tasks entirely. Explicitly setting both parameters
# avoids this issue and provides deterministic resource allocation across all tasks.
#
# Note: These parameter settings are optimized for quick tutorial demonstration. For production
# deployments requiring higher performance, we recommend adjusting both ``MAX_TRIALS_PER_TASK``
# and ``TOTAL_TRIALS`` to larger values. This allows more extensive search space exploration
# and typically yields better performance outcomes.
TOTAL_TRIALS = 512 # Change to 20000 for better performance if needed
MAX_TRIALS_PER_TASK = 16 # Change to more trials per task for better performance if needed
target = tvm.target.Target("nvidia/geforce-rtx-3090-ti") # Change to your target device
work_dir = "tuning_logs"
if not IS_IN_CI:
mod = relax.get_pipeline(
"static_shape_tuning",
target=target,
work_dir=work_dir,
total_trials=TOTAL_TRIALS,
max_trials_per_task=MAX_TRIALS_PER_TASK,
)(mod)
# Only show the main function
mod["main"].show()
######################################################################
# Build and Deploy
# ----------------
# Finally, we build the optimized model and deploy it to the target device.
# We skip this step in the CI environment.
if not IS_IN_CI:
with target:
mod = tvm.s_tir.transform.DefaultGPUSchedule()(mod)
ex = tvm.compile(mod, target=target)
dev = tvm.device("cuda", 0)
vm = relax.VirtualMachine(ex, dev)
# Need to allocate data and params on GPU device
gpu_data = tvm.runtime.tensor(np.random.rand(1, 3, 224, 224).astype("float32"), dev)
gpu_params = [tvm.runtime.tensor(p, dev) for p in params["main"]]
gpu_out = vm["main"](gpu_data, *gpu_params)[0].numpy()
print(gpu_out.shape)
@@ -0,0 +1,384 @@
# 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.
# ruff: noqa: E402
"""
.. _deploy_export_and_load_executable:
Export and Load Relax Executables
=================================
This tutorial walks through exporting a compiled Relax module to a shared
object, loading it back into the TVM runtime, and running the result either
interactively or from a standalone script. This tutorial demonstrates how
to turn Relax (or imported PyTorch / ONNX) programs into deployable artifacts
using ``tvm.relax`` APIs.
.. note::
This tutorial uses PyTorch as the source format, but the export/load workflow
is the same for ONNX models. For ONNX, use ``from_onnx(model, keep_params_in_input=True)``
instead of ``from_exported_program()``, then follow the same steps for building,
exporting, and loading.
"""
######################################################################
# Introduction
# ------------
# TVM builds Relax programs into ``tvm.runtime.Executable`` objects. These
# contain VM bytecode, compiled kernels, and constants. By exporting the
# executable with :py:meth:`export_library`, you obtain a shared library (for
# example ``.so`` on Linux) that can be shipped to another machine, uploaded
# via RPC, or loaded back later with the TVM runtime. This tutorial shows the
# exact steps end-to-end and explains what files are produced along the way.
import os
from pathlib import Path
try:
import torch
from torch.export import export
except ImportError: # pragma: no cover
torch = None # type: ignore
######################################################################
# Prepare a Torch MLP and Convert to Relax
# ----------------------------------------
# We start with a small PyTorch MLP so the example remains lightweight. The
# model is exported to a :py:class:`torch.export.ExportedProgram` and then
# translated into a Relax ``IRModule``.
import tvm_ffi
import tvm
from tvm import relax
from tvm.relax.frontend.torch import from_exported_program
# Check dependencies first
IS_IN_CI = os.getenv("CI", "").lower() == "true"
HAS_TORCH = torch is not None
RUN_EXAMPLE = HAS_TORCH and not IS_IN_CI
if HAS_TORCH:
class TorchMLP(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(28 * 28, 128),
torch.nn.ReLU(),
torch.nn.Linear(128, 10),
)
def forward(self, data: torch.Tensor) -> torch.Tensor: # type: ignore[override]
return self.net(data)
else: # pragma: no cover
TorchMLP = None # type: ignore[misc, assignment]
if RUN_EXAMPLE:
torch_model = TorchMLP().eval()
example_args = (torch.randn(1, 1, 28, 28, dtype=torch.float32),)
with torch.no_grad():
exported_program = export(torch_model, example_args)
mod = from_exported_program(exported_program, keep_params_as_input=True)
# Separate model parameters so they can be bound later (or stored on disk).
mod, params = relax.frontend.detach_params(mod)
print("Imported Relax module:")
mod.show()
######################################################################
# Build and Export with ``export_library``
# -------------------------------------------
# We build for ``llvm`` to generate CPU code and then export the resulting
# executable. Passing ``workspace_dir`` keeps the intermediate packaging files,
# which is useful to inspect what was produced.
TARGET = tvm.target.Target("llvm")
ARTIFACT_DIR = Path("relax_export_artifacts")
ARTIFACT_DIR.mkdir(exist_ok=True)
if RUN_EXAMPLE:
# Apply the default Relax compilation pipeline before building.
pipeline = relax.get_pipeline()
with TARGET:
built_mod = pipeline(mod)
# Build without params - we'll pass them at runtime
executable = tvm.compile(built_mod, target=TARGET)
library_path = ARTIFACT_DIR / "mlp_cpu.so"
executable.export_library(str(library_path), workspace_dir=str(ARTIFACT_DIR))
print(f"Exported runtime library to: {library_path}")
# The workspace directory now contains the shared object and supporting files.
produced_files = sorted(p.name for p in ARTIFACT_DIR.iterdir())
print("Artifacts saved:")
for name in produced_files:
print(f" - {name}")
# Generated files:
# - ``mlp_cpu.so``: The main deployable shared library containing VM bytecode,
# compiled kernels, and constants. Note: Since parameters are passed at runtime,
# you will also need to save a separate parameters file (see next section).
# - Intermediate object files (``devc.o``, ``lib0.o``, etc.) are kept in the
# workspace for inspection but are not required for deployment.
#
# Note: Additional files like ``*.params``, ``*.metadata.json``, or ``*.imports``
# may appear in specific configurations but are typically embedded into the
# shared library or only generated when needed.
######################################################################
# Load the Exported Library and Run It
# ------------------------------------
# Once the shared object is produced, we can reload it back into the TVM runtime
# on any machine with a compatible instruction set. The Relax VM consumes the
# runtime module directly.
if RUN_EXAMPLE:
loaded_rt_mod = tvm.runtime.load_module(str(library_path))
dev = tvm.cpu(0)
vm = relax.VirtualMachine(loaded_rt_mod, dev)
# Prepare input data
input_tensor = torch.randn(1, 1, 28, 28, dtype=torch.float32)
vm_input = tvm.runtime.tensor(input_tensor.numpy(), dev)
# Prepare parameters (allocate on target device)
vm_params = [tvm.runtime.tensor(p, dev) for p in params["main"]]
# Run inference: pass input data followed by all parameters
tvm_output = vm["main"](vm_input, *vm_params)
# TVM returns Array objects for tuple outputs, access via indexing.
# For models imported from PyTorch, outputs are typically tuples (even for single outputs).
# For ONNX models, outputs may be a single Tensor directly.
if isinstance(tvm_output, tvm_ffi.Array) and len(tvm_output) > 0:
result_tensor = tvm_output[0]
else:
result_tensor = tvm_output
print("VM output shape:", result_tensor.shape)
print("VM output type:", type(tvm_output), "->", type(result_tensor))
# You can still inspect the executable after reloading.
print("Executable stats:\n", loaded_rt_mod["stats"]())
######################################################################
# Save Parameters for Deployment
# -------------------------------
# Since parameters are passed at runtime (not embedded in the ``.so``), we must
# save them separately for deployment. This is a required step to use the model
# on other machines or in standalone scripts.
import numpy as np
if RUN_EXAMPLE:
# Save parameters to disk
params_path = ARTIFACT_DIR / "model_params.npz"
param_arrays = {f"p_{i}": p.numpy() for i, p in enumerate(params["main"])}
np.savez(str(params_path), **param_arrays)
print(f"Saved parameters to: {params_path}")
# Note: Alternatively, you can embed parameters directly into the ``.so`` to
# create a single-file deployment. Use ``keep_params_as_input=False`` when
# importing from PyTorch:
#
# .. code-block:: python
#
# mod = from_exported_program(exported_program, keep_params_as_input=False)
# # Parameters are now embedded as constants in the module
# executable = tvm.compile(built_mod, target=TARGET)
# # Runtime: vm["main"](input) # No need to pass params!
#
# This creates a single-file deployment (only the ``.so`` is needed), but you
# lose the flexibility to swap parameters without recompiling. For most
# production workflows, separating code and parameters (as shown above) is
# preferred for flexibility.
######################################################################
# Loading and Running the Exported Model
# -----------------------------------------------------------
# To use the exported model on another machine or in a standalone script, you need
# to load both the ``.so`` library and the parameters file. Here's a complete example
# of how to reload and run the model. Save this as ``run_mlp.py``:
#
# To make it executable from the command line:
#
# .. code-block:: bash
#
# chmod +x run_mlp.py
# ./run_mlp.py # Run it like a regular program
#
# Complete script:
#
# .. code-block:: python
#
# #!/usr/bin/env python3
# import numpy as np
# import tvm
# from tvm import relax
#
# # Step 1: Load the compiled library
# lib = tvm.runtime.load_module("relax_export_artifacts/mlp_cpu.so")
#
# # Step 2: Create Virtual Machine
# device = tvm.cpu(0)
# vm = relax.VirtualMachine(lib, device)
#
# # Step 3: Load parameters from the .npz file
# params_npz = np.load("relax_export_artifacts/model_params.npz")
# params = [tvm.runtime.tensor(params_npz[f"p_{i}"], device)
# for i in range(len(params_npz))]
#
# # Step 4: Prepare input data
# data = np.random.randn(1, 1, 28, 28).astype("float32")
# input_tensor = tvm.runtime.tensor(data, device)
#
# # Step 5: Run inference (pass input followed by all parameters)
# output = vm["main"](input_tensor, *params)
#
# # Step 6: Extract result (output may be tuple or single Tensor)
# # PyTorch models typically return tuples, ONNX models may return a single Tensor
# if isinstance(output, tvm_ffi.Array) and len(output) > 0:
# result_tensor = output[0]
# else:
# result_tensor = output
#
# print("Prediction shape:", result_tensor.shape)
# print("Predicted class:", np.argmax(result_tensor.numpy()))
#
# **Running on GPU:**
# To run on GPU instead of CPU, make the following changes:
#
# 1. **Compile for GPU** (earlier in the tutorial, around line 112):
#
# .. code-block:: python
#
# TARGET = tvm.target.Target("cuda") # Change from "llvm" to "cuda"
#
# 2. **Use GPU device in the script**:
#
# .. code-block:: python
#
# device = tvm.cuda(0) # Use CUDA device instead of CPU
# vm = relax.VirtualMachine(lib, device)
#
# # Load parameters to GPU
# params = [tvm.runtime.tensor(params_npz[f"p_{i}"], device) # Note: device parameter
# for i in range(len(params_npz))]
#
# # Prepare input on GPU
# input_tensor = tvm.runtime.tensor(data, device) # Note: device parameter
#
# The rest of the script remains the same. All tensors (parameters and inputs)
# must be allocated on the same device (GPU) as the compiled model.
#
# **Deployment Checklist:**
# When moving to another host (via RPC or SCP), you must copy **both** files:
#
# 1. ``mlp_cpu.so`` (or ``mlp_cuda.so`` for GPU) - the compiled model code
# 2. ``model_params.npz`` - the model parameters, serialized as NumPy arrays
#
# The remote machine needs both files in the same directory. The script above
# assumes they are in ``relax_export_artifacts/`` relative to the script location.
# Adjust the paths as needed for your deployment. For GPU deployment, ensure the
# target machine has compatible CUDA drivers and the model was compiled for the
# same GPU architecture.
######################################################################
# Deploying to Remote Devices
# ---------------------------
# To deploy the exported model to a remote ARM Linux device (e.g., Raspberry Pi),
# you can use TVM's RPC mechanism to cross-compile, upload, and run the model
# remotely. This workflow is useful when:
#
# - The target device has limited resources for compilation
# - You want to fine-tune performance by running on the actual hardware
# - You need to deploy to embedded devices
#
# See :doc:`cross_compilation_and_rpc </how_to/tutorials/cross_compilation_and_rpc>`
# for a comprehensive guide on:
#
# - Setting up TVM runtime on the remote device
# - Starting an RPC server on the device
# - Cross-compiling for ARM targets (e.g., ``llvm -mtriple=aarch64-linux-gnu``)
# - Uploading exported libraries via RPC
# - Running inference remotely
#
# Quick example for ARM deployment workflow:
#
# .. code-block:: python
#
# import tvm.rpc as rpc
# from tvm import relax
#
# # Step 1: Cross-compile for ARM target (on local machine)
# TARGET = tvm.target.Target({"kind": "llvm", "mtriple": "aarch64-linux-gnu"})
# executable = tvm.compile(built_mod, target=TARGET)
# executable.export_library("mlp_arm.so")
#
# # Step 2: Connect to remote device RPC server
# remote = rpc.connect("192.168.1.100", 9090) # Device IP and RPC port
#
# # Step 3: Upload the compiled library and parameters
# remote.upload("mlp_arm.so")
# remote.upload("model_params.npz")
#
# # Step 4: Load and run on remote device
# lib = remote.load_module("mlp_arm.so")
# vm = relax.VirtualMachine(lib, remote.cpu())
# # ... prepare input and params, then run inference
#
# The key difference is using an ARM target triple during compilation and
# uploading files via RPC instead of copying them directly.
######################################################################
# FAQ
# ---
# **Can I run the ``.so`` as a standalone executable (like ``./mlp_cpu.so``)?**
# No. The ``.so`` file is a shared library, not a standalone executable binary.
# You cannot run it directly from the terminal. It must be loaded through a TVM
# runtime program (as shown in the "Loading and Running" section above). The
# ``.so`` bundles VM bytecode and compiled kernels, but still requires the TVM
# runtime to execute.
#
# **Which devices can run the exported library?**
# The target must match the ISA you compiled for (``llvm`` in this example).
# As long as the target triple, runtime ABI, and available devices line up,
# you can move the artifact between machines. For heterogeneous builds (CPU
# plus GPU), ship the extra device libraries as well.
#
# **What about the ``.params`` and ``metadata.json`` files?**
# These auxiliary files are only generated in specific configurations. In this
# tutorial, since we pass parameters at runtime, they are not generated. When
# they do appear, they may be kept alongside the ``.so`` for inspection, but
# the essential content is typically embedded in the shared object itself, so
# deploying the ``.so`` alone is usually sufficient.
+407
View File
@@ -0,0 +1,407 @@
# 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.
# ruff: noqa: E402, E501
"""
.. _import_model:
Importing Models from ML Frameworks
====================================
Apache TVM supports importing models from popular ML frameworks including PyTorch, ONNX,
and TensorFlow Lite. This tutorial walks through each import path with a minimal working
example and explains the key parameters. The PyTorch section additionally demonstrates
how to handle unsupported operators via a custom converter map.
For end-to-end optimization and deployment after importing, see :ref:`optimize_model`.
.. note::
The ONNX section requires the ``onnx`` package. The TFLite section requires
``tensorflow`` and ``tflite``. Sections whose dependencies are missing are skipped
automatically.
.. contents:: Table of Contents
:local:
:depth: 2
"""
######################################################################
# Importing from PyTorch (Recommended)
# -------------------------------------
# TVM's PyTorch frontend is the most feature-complete. The recommended entry point is
# :py:func:`~tvm.relax.frontend.torch.from_exported_program`, which works with PyTorch's
# ``torch.export`` API.
#
# We start by defining a small CNN model for demonstration. No pretrained weights are
# needed — we only care about the graph structure.
import numpy as np
import torch
from torch import nn
from torch.export import export
import tvm
from tvm import relax
from tvm.relax.frontend.torch import from_exported_program
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.bn = nn.BatchNorm2d(16)
self.pool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(16, 10)
def forward(self, x):
x = torch.relu(self.bn(self.conv(x)))
x = self.pool(x).flatten(1)
x = self.fc(x)
return x
torch_model = SimpleCNN().eval()
example_args = (torch.randn(1, 3, 32, 32),)
######################################################################
# Basic import
# ~~~~~~~~~~~~
# The standard workflow is: ``torch.export.export()`` → ``from_exported_program()`` →
# ``detach_params()``.
with torch.no_grad():
exported_program = export(torch_model, example_args)
mod = from_exported_program(
exported_program,
keep_params_as_input=True,
unwrap_unit_return_tuple=True,
)
mod, params = relax.frontend.detach_params(mod)
mod.show()
######################################################################
# Key parameters
# ~~~~~~~~~~~~~~
# ``from_exported_program`` accepts several parameters that control how the model is
# translated:
#
# - **keep_params_as_input** (bool, default ``False``): When ``True``, model weights become
# function parameters, separated via ``relax.frontend.detach_params()``. When ``False``,
# weights are embedded as constants inside the IRModule. Use ``True`` when you want to
# manage weights independently (e.g., for weight sharing or quantization).
#
# - **unwrap_unit_return_tuple** (bool, default ``False``): PyTorch ``export`` always wraps
# the return value in a tuple. Set ``True`` to unwrap single-element return tuples for a
# cleaner Relax function signature.
#
# - **run_ep_decomposition** (bool, default ``True``): Runs PyTorch's built-in operator
# decomposition before translation. This breaks high-level ops (e.g., ``batch_norm``) into
# lower-level primitives, which generally improves TVM's coverage and optimization
# opportunities. Set ``False`` if you want to preserve the original op granularity.
######################################################################
# Handling unsupported operators with ``custom_convert_map``
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# When TVM encounters a PyTorch operator it does not recognize, it raises an error
# indicating the unsupported operator name. You can extend the frontend by providing a
# **custom converter map** — a dictionary mapping operator names to your own conversion
# functions.
#
# A custom converter function receives two arguments:
#
# - **node** (``torch.fx.Node``): The FX graph node being converted, carrying operator
# info and references to input nodes.
# - **importer** (``ExportedProgramImporter``): The importer instance, giving access to:
#
# - ``importer.env``: Dict mapping FX nodes to their converted Relax expressions.
# - ``importer.block_builder``: The Relax ``BlockBuilder`` for emitting operations.
# - ``importer.retrieve_args(node)``: Helper to look up converted args.
#
# The function must return a ``relax.Var`` — the Relax expression for this node's output.
# Here is an example that maps an operator to ``relax.op.sigmoid``:
from tvm.relax.frontend.torch.exported_program_translator import ExportedProgramImporter
def convert_sigmoid(node: torch.fx.Node, importer: ExportedProgramImporter) -> relax.Var:
"""Custom converter: map an op to relax.op.sigmoid."""
args = importer.retrieve_args(node)
return importer.block_builder.emit(relax.op.sigmoid(args[0]))
######################################################################
# To use the custom converter, pass it via the ``custom_convert_map`` parameter. The key
# is the ATen operator name in ``"op_name.variant"`` format (e.g., ``"sigmoid.default"``):
#
# .. code-block:: python
#
# mod = from_exported_program(
# exported_program,
# custom_convert_map={"sigmoid.default": convert_sigmoid},
# )
#
# .. note::
#
# To find the correct operator name, check the error message TVM raises when encountering
# the unsupported op — it includes the exact ATen name. You can also inspect the exported
# program's graph via ``print(exported_program.graph_module.graph)`` to see all operator
# names.
######################################################################
# Alternative PyTorch import methods
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Besides ``from_exported_program``, TVM also provides:
#
# - :py:func:`~tvm.relax.frontend.torch.from_fx`: Works with ``torch.fx.GraphModule``
# from ``torch.fx.symbolic_trace()``. Requires explicit ``input_info`` (shapes and dtypes).
# Use this when ``torch.export`` fails on certain Python control flow patterns.
#
# - :py:func:`~tvm.relax.frontend.torch.relax_dynamo`: A ``torch.compile`` backend that
# compiles and executes the model through TVM in one step. Useful for integrating TVM
# into an existing PyTorch training or inference loop.
#
# - :py:func:`~tvm.relax.frontend.torch.dynamo_capture_subgraphs`: Captures subgraphs from
# a PyTorch model into an IRModule via ``torch.compile``. Each subgraph becomes a separate
# function in the IRModule.
#
# For most use cases, ``from_exported_program`` is the recommended path.
######################################################################
# Verifying the imported model
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# After importing, it is good practice to verify that TVM produces the same output as the
# original framework. We compile with the minimal ``"zero"`` pipeline (no tuning) and
# compare. The same approach applies to models imported via the ONNX and TFLite frontends
# shown below.
mod_compiled = relax.get_pipeline("zero")(mod)
exec_module = tvm.compile(mod_compiled, target="llvm")
dev = tvm.cpu()
vm = relax.VirtualMachine(exec_module, dev)
# Run inference
input_data = np.random.rand(1, 3, 32, 32).astype("float32")
tvm_input = tvm.runtime.tensor(input_data, dev)
tvm_params = [tvm.runtime.tensor(p, dev) for p in params["main"]]
tvm_out = vm["main"](tvm_input, *tvm_params).numpy()
# Compare with PyTorch
with torch.no_grad():
pt_out = torch_model(torch.from_numpy(input_data)).numpy()
np.testing.assert_allclose(tvm_out, pt_out, rtol=1e-5, atol=1e-5)
print("PyTorch vs TVM outputs match!")
######################################################################
# Importing from ONNX
# --------------------
# TVM can import ONNX models via :py:func:`~tvm.relax.frontend.onnx.from_onnx`. The
# function accepts an ``onnx.ModelProto`` object, so you need to load the model with
# ``onnx.load()`` first.
#
# Here we export the same CNN model to ONNX format and then import it into TVM.
try:
import onnx
import onnxscript # noqa: F401 # required by torch.onnx.export
HAS_ONNX = True
except ImportError:
onnx = None # type: ignore[assignment]
HAS_ONNX = False
if HAS_ONNX:
from tvm.relax.frontend.onnx import from_onnx
# Export the PyTorch model to ONNX
dummy_input = torch.randn(1, 3, 32, 32)
onnx_path = "simple_cnn.onnx"
torch.onnx.export(torch_model, dummy_input, onnx_path, input_names=["input"])
# Load and import into TVM
onnx_model = onnx.load(onnx_path)
mod_onnx = from_onnx(onnx_model, keep_params_in_input=True)
mod_onnx, params_onnx = relax.frontend.detach_params(mod_onnx)
mod_onnx.show()
######################################################################
# If you already have an ``.onnx`` file on disk, the workflow is even simpler:
#
# .. code-block:: python
#
# import onnx
# from tvm.relax.frontend.onnx import from_onnx
#
# onnx_model = onnx.load("my_model.onnx")
# mod = from_onnx(onnx_model)
#
######################################################################
# Key parameters
# ~~~~~~~~~~~~~~
# - **shape_dict** (dict, optional): Maps input names to shapes. Auto-inferred from the
# model if not provided. Useful when the ONNX model has dynamic dimensions that you
# want to fix to concrete sizes:
#
# .. code-block:: python
#
# mod = from_onnx(onnx_model, shape_dict={"input": [1, 3, 224, 224]})
#
# - **dtype_dict** (str or dict, default ``"float32"``): Input dtypes. A single string
# applies to all inputs, or use a dict to set per-input dtypes:
#
# .. code-block:: python
#
# mod = from_onnx(onnx_model, dtype_dict={"input": "float16"})
#
# - **keep_params_in_input** (bool, default ``False``): Same semantics as PyTorch — whether
# model weights are function parameters or embedded constants.
#
# - **opset** (int, optional): Override the opset version auto-detected from the model.
# Each ONNX op may have different semantics across opset versions; TVM's converter
# selects the appropriate implementation automatically. You rarely need to set this
# unless the model metadata is incorrect.
######################################################################
# Importing from TensorFlow Lite
# -------------------------------
# TVM can import TFLite flat buffer models via
# :py:func:`~tvm.relax.frontend.tflite.from_tflite`. The function expects a TFLite
# ``Model`` object parsed from flat buffer bytes via ``GetRootAsModel``.
#
# .. note::
#
# The ``tflite`` Python package has changed its module layout across versions.
# Older versions use ``tflite.Model.Model.GetRootAsModel``, while newer versions use
# ``tflite.Model.GetRootAsModel``. The code below handles both.
#
# Below we create a minimal TFLite model from TensorFlow and import it.
try:
import tensorflow as tf
import tflite
import tflite.Model
HAS_TFLITE = True
except ImportError:
HAS_TFLITE = False
if HAS_TFLITE:
from tvm.relax.frontend.tflite import from_tflite
# Define a simple TF module and convert to TFLite.
# We use plain TF ops (not keras layers) to avoid variable-handling ops
# that some TFLite converter versions do not support cleanly.
class TFModule(tf.Module):
@tf.function(
input_signature=[
tf.TensorSpec(shape=(1, 784), dtype=tf.float32),
tf.TensorSpec(shape=(784, 10), dtype=tf.float32),
]
)
def forward(self, x, weight):
return tf.matmul(x, weight) + 0.1
tf_module = TFModule()
converter = tf.lite.TFLiteConverter.from_concrete_functions(
[tf_module.forward.get_concrete_function()], tf_module
)
tflite_buf = converter.convert()
# Parse and import into TVM (API differs between tflite package versions)
if hasattr(tflite.Model, "Model"):
tflite_model = tflite.Model.Model.GetRootAsModel(tflite_buf, 0)
else:
tflite_model = tflite.Model.GetRootAsModel(tflite_buf, 0)
mod_tflite = from_tflite(tflite_model)
mod_tflite.show()
######################################################################
# Loading from a ``.tflite`` file
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# If you already have a ``.tflite`` file on disk, load the raw bytes and parse them:
#
# .. code-block:: python
#
# import tflite
# import tflite.Model
# from tvm.relax.frontend.tflite import from_tflite
#
# with open("my_model.tflite", "rb") as f:
# tflite_buf = f.read()
#
# if hasattr(tflite.Model, "Model"):
# tflite_model = tflite.Model.Model.GetRootAsModel(tflite_buf, 0)
# else:
# tflite_model = tflite.Model.GetRootAsModel(tflite_buf, 0)
# mod = from_tflite(tflite_model)
######################################################################
# Key parameters
# ~~~~~~~~~~~~~~
# - **shape_dict** / **dtype_dict** (optional): Override input shapes and dtypes. If not
# provided, they are inferred from the TFLite model metadata.
#
# - **op_converter** (class, optional): A custom operator converter class. Subclass
# ``OperatorConverter`` and override its ``convert_map`` dictionary to add or replace
# operator conversions. For example, to add a hypothetical ``CUSTOM_RELU`` op:
#
# .. code-block:: python
#
# from tvm.relax.frontend.tflite.tflite_frontend import OperatorConverter
#
# class MyConverter(OperatorConverter):
# def __init__(self, model, subgraph, exp_tab, ctx):
# super().__init__(model, subgraph, exp_tab, ctx)
# self.convert_map["CUSTOM_RELU"] = self._convert_custom_relu
#
# def _convert_custom_relu(self, op):
# # implement your conversion logic here
# ...
#
# mod = from_tflite(tflite_model, op_converter=MyConverter)
######################################################################
# Summary
# -------
#
# +---------------------+----------------------------+-------------------------------+-----------------------------+
# | Aspect | PyTorch | ONNX | TFLite |
# +=====================+============================+===============================+=============================+
# | Entry function | ``from_exported_program`` | ``from_onnx`` | ``from_tflite`` |
# +---------------------+----------------------------+-------------------------------+-----------------------------+
# | Input | ``ExportedProgram`` | ``onnx.ModelProto`` | TFLite ``Model`` object |
# +---------------------+----------------------------+-------------------------------+-----------------------------+
# | Custom extension | ``custom_convert_map`` | — | ``op_converter`` class |
# +---------------------+----------------------------+-------------------------------+-----------------------------+
#
# **Which to use?** Pick the frontend that matches your model format:
#
# - Have a PyTorch model? Use ``from_exported_program`` — it has the broadest operator coverage.
# - Have an ``.onnx`` file? Use ``from_onnx``.
# - Have a ``.tflite`` file? Use ``from_tflite``.
#
# The verification workflow (compile → run → compare) demonstrated in the PyTorch section
# above applies equally to ONNX and TFLite imports.
#
# For the full list of supported operators, see the converter map in each frontend's source:
# PyTorch uses ``create_convert_map()`` in ``exported_program_translator.py``, ONNX uses
# ``_get_convert_map()`` in ``onnx_frontend.py``, and TFLite uses ``convert_map`` in
# ``OperatorConverter`` in ``tflite_frontend.py``.
#
# After importing, refer to :ref:`optimize_model` for optimization and deployment.
@@ -0,0 +1,461 @@
# 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.
"""
.. _mix_python_and_tvm:
Mix Python/PyTorch with TVM Using BasePyModule
===============================================
In a typical TVM workflow, you write an ``IRModule``, compile it, and load the compiled artifact
into a ``VirtualMachine`` to run. This means **you cannot test or debug anything until the entire
module compiles successfully**. If a single op is unsupported, the whole pipeline is blocked.
``BasePyModule`` solves this by letting Python functions, TIR kernels, and Relax functions coexist
in one module. TIR and Relax functions are JIT-compiled on instantiation, Python functions run
as-is, and tensors move between TVM and PyTorch via zero-copy DLPack. This enables:
- **Incremental development**: get a model running with Python fallbacks first, then replace them
with TVM ops one by one.
- **Easy debugging**: insert ``print`` in Python functions to inspect intermediate tensors — no
need to compile the whole module first.
- **Verification at any compilation stage**: convert Relax IR back to PyTorch to check numerical
correctness before and after optimization passes.
- **Hybrid execution**: let the compiled VM call back into Python for ops that are hard to
express in TIR or Relax.
This tutorial walks through the full workflow step by step.
.. contents:: Table of Contents
:local:
:depth: 1
"""
######################################################################
# Preparation
# -----------
import os
try:
import torch
import torch.nn.functional as F
except ImportError:
torch = None
import tvm
from tvm import relax
from tvm.relax.base_py_module import BasePyModule
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T
IS_IN_CI = os.getenv("CI", "").lower() == "true"
HAS_TORCH = torch is not None
RUN_EXAMPLE = HAS_TORCH and not IS_IN_CI
######################################################################
# Step 1: Your First Hybrid Module
# ----------------------------------
# The core idea: decorate a class with ``@I.ir_module``, inherit from ``BasePyModule``, and use
# three decorators for three kinds of functions:
#
# - ``@T.prim_func`` — low-level TIR kernel (JIT-compiled on instantiation)
# - ``@R.function`` — high-level Relax graph (JIT-compiled on instantiation)
# - ``@I.pyfunc`` — plain Python (runs as-is, can use any Python library)
#
# ``call_tir`` bridges Python and TIR: it converts PyTorch tensors to TVM NDArrays via DLPack
# (zero-copy), allocates the output buffer, calls the compiled kernel, and converts back.
if RUN_EXAMPLE:
@I.ir_module
class MyFirstModule(BasePyModule):
@T.prim_func(s_tir=True)
def add_tir(
A: T.Buffer((4,), "float32"),
B: T.Buffer((4,), "float32"),
C: T.Buffer((4,), "float32"),
):
for i in range(4):
C[i] = A[i] + B[i]
@I.pyfunc
def forward(self, x, y):
"""Takes PyTorch tensors, calls TIR, returns PyTorch tensors."""
x_tvm = self._convert_pytorch_to_tvm(x)
y_tvm = self._convert_pytorch_to_tvm(y)
result = self.call_tir(self.add_tir, [x_tvm, y_tvm], out_ty=R.Tensor((4,), "float32"))
return self._convert_tvm_to_pytorch(result)
# TIR functions are JIT-compiled at instantiation
mod = MyFirstModule(device=tvm.cpu(0))
x = torch.tensor([1.0, 2.0, 3.0, 4.0])
y = torch.tensor([10.0, 20.0, 30.0, 40.0])
result = mod.forward(x, y)
print("forward(x, y) =", result)
assert torch.allclose(result, x + y)
# show() prints TVMScript including Python functions (shown as ExternFunc)
mod.show()
# list_functions() shows what is available in the module
print("Available functions:", mod.list_functions())
######################################################################
# Step 2: Debugging — The Main Selling Point
# ---------------------------------------------
# Traditional ML compilers treat computation graphs as monolithic blobs. You cannot inspect
# intermediate tensor values without compiling the entire module. With ``@I.pyfunc``, debugging
# is as simple as adding a ``print`` statement. You can also make quick edits and re-run
# immediately — no recompilation needed.
if RUN_EXAMPLE:
@I.ir_module
class DebugModule(BasePyModule):
@T.prim_func(s_tir=True)
def matmul_tir(var_A: T.handle, var_B: T.handle, var_C: T.handle):
n = T.int32()
A = T.match_buffer(var_A, (n, 4), "float32")
B = T.match_buffer(var_B, (4, 3), "float32")
C = T.match_buffer(var_C, (n, 3), "float32")
for i, j, k in T.grid(n, 3, 4):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
@I.pyfunc
def forward(self, x, weights):
# Inspect input
print(f" [DEBUG] input shape: {x.shape}, mean: {x.mean():.4f}")
# Run TIR matmul
x_tvm = self._convert_pytorch_to_tvm(x)
w_tvm = self._convert_pytorch_to_tvm(weights)
out = self.call_tir(
self.matmul_tir,
[x_tvm, w_tvm],
out_ty=R.Tensor((x.shape[0], 3), "float32"),
)
logits = self._convert_tvm_to_pytorch(out)
# Inspect intermediate value — impossible with a compiled-only workflow
print(
f" [DEBUG] logits shape: {logits.shape}, "
f"min: {logits.min():.4f}, max: {logits.max():.4f}"
)
result = F.softmax(logits, dim=-1)
# Verify output
print(f" [DEBUG] probs sum: {result.sum(dim=-1)}")
return result
mod = DebugModule(device=tvm.cpu(0))
print("Running with debug prints:")
probs = mod.forward(torch.randn(2, 4), torch.randn(4, 3))
assert torch.allclose(probs.sum(dim=-1), torch.ones(2), atol=1e-5)
######################################################################
# This is the key benefit: "debugging is as simple as inserting a print statement.
# Users can also make quick, manual edits to Python functions and immediately observe the
# results." No compilation cycle, no VM loading — just Python.
######################################################################
# Step 3: A Realistic Pipeline — Python, TIR, and Packed Functions
# -------------------------------------------------------------------
# Real models combine many kinds of operations. This step builds a mini inference pipeline using
# three different calling conventions:
#
# - ``call_tir``: call a compiled TIR kernel
# - ``call_dps_packed``: call a TVM packed function (e.g., a third-party library binding)
# - Direct Python: call any PyTorch function
#
# ``call_dps_packed`` is useful for calling functions registered via ``tvm.register_global_func``
# — for example, CUBLAS or cuDNN bindings that TVM wraps as packed functions.
if RUN_EXAMPLE:
# Register a packed function (simulating an external library binding)
@tvm.register_global_func("my_bias_add", override=True)
def my_bias_add(x, bias, out):
"""Packed function: adds bias to each row of x."""
x_np = x.numpy()
b_np = bias.numpy()
out_np = x_np + b_np
out[:] = out_np
@I.ir_module
class PipelineModule(BasePyModule):
@T.prim_func(s_tir=True)
def matmul_tir(var_A: T.handle, var_B: T.handle, var_C: T.handle):
A = T.match_buffer(var_A, (2, 4), "float32")
B = T.match_buffer(var_B, (4, 3), "float32")
C = T.match_buffer(var_C, (2, 3), "float32")
for i, j, k in T.grid(2, 3, 4):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
@I.pyfunc
def forward(self, x, weights, bias):
# 1. TIR matmul
x_tvm = self._convert_pytorch_to_tvm(x)
w_tvm = self._convert_pytorch_to_tvm(weights)
h = self.call_tir(
self.matmul_tir,
[x_tvm, w_tvm],
out_ty=R.Tensor((2, 3), "float32"),
)
h_pt = self._convert_tvm_to_pytorch(h)
# 2. Packed function for bias add (simulating an external library)
h_biased = self.call_dps_packed(
"my_bias_add",
[h_pt, bias],
out_ty=R.Tensor((2, 3), "float32"),
)
# 3. Python/PyTorch activation
return F.relu(h_biased)
mod = PipelineModule(device=tvm.cpu(0))
x = torch.randn(2, 4)
w = torch.randn(4, 3)
b = torch.randn(3)
result = mod.forward(x, w, b)
expected = F.relu(x @ w + b)
print("Pipeline result:", result)
print("Expected: ", expected)
assert torch.allclose(result, expected, atol=1e-4)
######################################################################
# Step 4: Relax-to-Python Converter — Verify at Any Compilation Stage
# ----------------------------------------------------------------------
# Both Relax functions and Python functions describe computational graphs. The
# ``RelaxToPyFuncConverter`` converts Relax IR into equivalent PyTorch code by mapping
# Relax operators to their PyTorch counterparts (e.g., ``R.nn.relu`` → ``F.relu``).
#
# A key feature: **this conversion can happen at any stage of compilation**.
# You can convert early (right after import) or late (after optimization passes have
# transformed the IR), and compare the output against a PyTorch reference to catch bugs.
if RUN_EXAMPLE:
from tvm.relax.relax_to_pyfunc_converter import RelaxToPyFuncConverter
# A simple Relax module: matmul + bias + relu (a dense layer)
@I.ir_module
class DenseLayer:
@T.prim_func(s_tir=True)
def bias_add_tir(var_x: T.handle, var_b: T.handle, var_out: T.handle):
x = T.match_buffer(var_x, (2, 4), "float32")
b = T.match_buffer(var_b, (4,), "float32")
out = T.match_buffer(var_out, (2, 4), "float32")
for i, j in T.grid(2, 4):
out[i, j] = x[i, j] + b[j]
@R.function
def main(
x: R.Tensor((2, 4), "float32"),
w: R.Tensor((4, 4), "float32"),
b: R.Tensor((4,), "float32"),
) -> R.Tensor((2, 4), "float32"):
h = R.matmul(x, w)
cls = DenseLayer
h_bias = R.call_tir(
cls.bias_add_tir,
(h, b),
out_ty=R.Tensor((2, 4), "float32"),
)
return R.nn.relu(h_bias)
# --- Stage 1: Convert BEFORE optimization ---
converter = RelaxToPyFuncConverter(DenseLayer)
converted_early = converter.convert(["main"])
x = torch.randn(2, 4)
w = torch.randn(4, 4)
b = torch.randn(4)
py_result_early = converted_early.pyfuncs["main"](x, w, b)
expected = F.relu(x @ w + b)
print("Before optimization:")
print(" Converted result:", py_result_early)
print(" PyTorch expected:", expected)
assert torch.allclose(py_result_early, expected, atol=1e-5)
# --- Stage 2: Apply a pass, then convert AFTER optimization ---
# Run CanonicalizeBindings to clean up the IR, then convert again
# to verify the pass did not break numerical correctness.
optimized_mod = relax.transform.CanonicalizeBindings()(DenseLayer)
converter_late = RelaxToPyFuncConverter(optimized_mod)
converted_late = converter_late.convert(["main"])
py_result_late = converted_late.pyfuncs["main"](x, w, b)
print("\nAfter CanonicalizeBindings pass:")
print(" Converted result:", py_result_late)
print(" Still matches: ", torch.allclose(py_result_late, expected, atol=1e-5))
assert torch.allclose(py_result_late, expected, atol=1e-5)
######################################################################
# Step 5: R.call_py_func — Python Callbacks in Compiled IR
# -----------------------------------------------------------
# ``R.call_py_func`` embeds a Python function call directly inside Relax IR. When the module
# is compiled and run in the VM, everything else is optimized native code, but the VM calls
# back into Python for the specified ops.
#
# ``BasePyModule`` supports cross-level calls in both directions: Relax functions can invoke
# Python functions, and Python functions can invoke TIR/Relax functions. Data flows between
# them via DLPack with minimal overhead.
#
# Use case: your model has a custom op (e.g., a special normalization or a sampling step)
# that is complex to implement in TIR. Compile everything else, and let that one op stay
# in Python.
if RUN_EXAMPLE:
@I.ir_module
class HybridVMModule(BasePyModule):
@I.pyfunc
def silu(self, x):
"""SiLU/Swish activation — using Python as fallback."""
return torch.sigmoid(x) * x
@I.pyfunc
def layer_norm(self, x):
"""LayerNorm — another Python fallback."""
return F.layer_norm(x, x.shape[-1:])
@R.function
def main(
x: R.Tensor((4, 8), "float32"),
) -> R.Tensor((4, 8), "float32"):
# The VM calls back into Python for these two ops
h = R.call_py_func("layer_norm", (x,), out_ty=R.Tensor((4, 8), "float32"))
out = R.call_py_func("silu", (h,), out_ty=R.Tensor((4, 8), "float32"))
return out
mod = HybridVMModule(device=tvm.cpu(0))
x = torch.randn(4, 8)
# call_py_func is also callable from Python directly
result = mod.call_py_func("layer_norm", [x])
result = mod.call_py_func("silu", [result])
ln = F.layer_norm(x, x.shape[-1:])
expected = torch.sigmoid(ln) * ln
print("call_py_func result:", result)
assert torch.allclose(torch.tensor(result.numpy()), expected, atol=1e-5)
######################################################################
# Step 6: Cross-Level Calls and Symbolic Shapes
# ------------------------------------------------
# ``BasePyModule`` is designed for **cross-level interoperability**: Python functions can call
# TIR and Relax functions, and Relax functions can call Python functions. We have already seen:
#
# - Python → TIR via ``call_tir`` (Steps 1-3)
# - Python → packed function via ``call_dps_packed`` (Step 3)
# - Relax → Python via ``R.call_py_func`` (Step 5)
#
# The missing piece: **Python calling a compiled Relax function directly**. When a module
# contains ``@R.function``, it is JIT-compiled into a Relax VM. You can call it from Python
# just like any other method — the module auto-converts PyTorch tensors to TVM and back.
#
# This step also shows **symbolic shapes**: TIR and Relax functions can declare dynamic
# dimensions (e.g., ``"n"``). ``BasePyModule`` infers concrete shapes from the actual input
# tensors at call time, so the same module handles different sizes without recompilation.
if RUN_EXAMPLE:
@I.ir_module
class DynamicModule(BasePyModule):
@T.prim_func(s_tir=True)
def scale_tir(var_x: T.handle, var_out: T.handle):
n = T.int64()
x = T.match_buffer(var_x, (n,), "float32")
out = T.match_buffer(var_out, (n,), "float32")
for i in T.serial(n):
out[i] = x[i] * T.float32(2.0)
@R.function
def add_relax(
x: R.Tensor(("n",), "float32"),
y: R.Tensor(("n",), "float32"),
) -> R.Tensor(("n",), "float32"):
return R.add(x, y)
mod = DynamicModule(device=tvm.cpu(0), target="llvm")
# Inspect what the module contains
print("Functions:", mod.list_functions())
# Python → Relax: call the compiled Relax function directly with PyTorch tensors
a5 = torch.randn(5)
b5 = torch.randn(5)
out5 = mod.add_relax(a5, b5)
print("add_relax(len=5):", out5)
# Same module, different size — symbolic shapes handle this automatically
a10 = torch.randn(10)
b10 = torch.randn(10)
out10 = mod.add_relax(a10, b10)
print("add_relax(len=10):", out10)
# Python → TIR with symbolic output shape
n = T.int64()
x7 = torch.randn(7)
scaled = mod.call_tir("scale_tir", [x7], relax.TensorType((n,), "float32"))
print("scale_tir(len=7):", scaled)
assert torch.allclose(torch.tensor(scaled.numpy()), x7 * 2.0, atol=1e-5)
######################################################################
# Summary
# -------
# Cross-level call summary:
#
# - **Python → TIR**: ``call_tir()`` (Steps 1, 2, 3, 6)
# - **Python → packed function**: ``call_dps_packed()`` (Step 3)
# - **Python → Relax**: call ``@R.function`` as a method (Step 6)
# - **Relax → Python**: ``R.call_py_func()`` in compiled VM (Step 5)
#
# The workflow in practice:
#
# 1. Import a model → some ops unsupported → use ``@I.pyfunc`` as Python fallbacks
# 2. Get it running end-to-end with ``BasePyModule``
# 3. Debug by inserting ``print`` in pyfuncs — inspect intermediate tensors instantly
# 4. Use ``RelaxToPyFuncConverter`` to verify correctness after each optimization pass
# 5. Gradually replace Python fallbacks with TIR/Relax implementations
# 6. Use ``R.call_py_func`` for ops that must stay in Python even after compilation
+622
View File
@@ -0,0 +1,622 @@
# 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.
# ruff: noqa: F401
"""
.. _opt_llm:
Optimize Large Language Model
=============================
As large language models (LLMs) have become a popular research topic in many different fields,
deploying them on cloud and edge devices has become a challenging task. In this tutorial, we will
demonstrate how to optimize a large language model using Apache TVM. We will use a pre-trained
TinyLlama model from Hugging Face and deploy it on various devices.
"""
######################################################################
# Review Overall Flow
# -------------------
# .. figure:: https://raw.githubusercontent.com/tlc-pack/web-data/main/images/design/tvm_overall_flow.svg
# :align: center
# :width: 80%
#
# The overall flow consists of the following steps:
#
# - **Construct or Import a Model**: Construct a neural network model or import a pre-trained
# model from other frameworks (e.g. PyTorch, ONNX), and create the TVM IRModule, which contains
# all the information needed for compilation, including high-level Relax functions for
# computational graph, and low-level TensorIR functions for tensor program.
# - **Perform Composable Optimizations**: Perform a series of optimization transformations,
# such as graph optimizations, tensor program optimizations, and library dispatching.
# - **Build and Universal Deployment**: Build the optimized model to a deployable module to the
# universal runtime, and execute it on different devices, such as CPU, GPU, or other accelerators.
#
######################################################################
# Construct the model architecture
# --------------------------------
# We will use a pre-trained TinyLlama model from Hugging Face. However, usually we only load the
# pre-trained weight from Hugging Face but not the model architecture. We need to construct the
# model architecture by ourselves. Apache TVM prepares a PyTorch-liked API to construct the model
# architecture. We can use the API to construct the model architecture.
import dataclasses
import enum
import os
from pathlib import Path
from pprint import pprint
from tvm_ffi import Shape
import tvm
from tvm import relax, te, tirx
from tvm.relax import register_pipeline
from tvm.relax.frontend import nn
from tvm.relax.frontend.nn import Tensor, op
from tvm.relax.frontend.nn.llm.kv_cache import PagedKVCache, TIRPagedKVCache
from tvm.s_tir import dlight
######################################################################
# First, we need to define the model configuration. The configuration includes the key parameters
# of the model, such as hidden size, intermediate size, etc. Here for convenience, we define a
# constant config specially for the TinyLlama model.
@dataclasses.dataclass
class LlamaConfig:
hidden_size: int = 2048
intermediate_size: int = 5632
num_attention_heads: int = 32
num_hidden_layers: int = 22
rms_norm_eps: float = 1e-05
vocab_size: int = 32000
rope_theta: int = 10000
context_window_size: int = 2048
prefill_chunk_size: int = 2048
num_key_value_heads: int = 4
head_dim: int = 64 # hidden_size // num_attention_heads
dev = tvm.device("cuda", 0)
target = tvm.target.Target.from_device(dev)
######################################################################
# Next, we define the RoPE mode of the Paged KV cache. The RoPE mode is used to apply the
# Relative Positional Encoding (RoPE) to the query and key tensors. The RoPE mode can be set to
# `NONE`, `NORMAL`, or `INLINE`. If the RoPE mode is `NONE`, the KV cache will not apply RoPE to
# the query and key tensors. If the RoPE mode is `NORMAL`, RoPE will be applied to the key tensor
# before adding the key tensor to the cache. If the RoPE mode is `INLINE`, RoPE will be applied to
# the query and key tensors in the attention kernel on-the-fly.
class RopeMode(enum.IntEnum):
"""The RoPE mode of the Paged KV cache.
If it is none, the KV cache will not apply RoPE to q and k.
If it is normal, RoPE will be applied to k before adding k to cache.
Otherwise, RoPE will be applied to q/k in attention kernel on-the-fly.
"""
NONE = 0
NORMAL = 1
INLINE = 2
######################################################################
# Secondly, we define the model architecture. The model architecture consists of three parts:
#
# - Embedding layer: The embedding layer converts the input token IDs to the hidden states.
# - Decoder layers: The decoder layers are the core of the model. Each decoder layer consists of
# a self-attention layer and a feed-forward network (FFN) layer.
# - Output layer: The output layer converts the hidden states to the logits.
#
# First we define the FFN layer. Note that the following FFN layer is optimized implementation
# where we fuse the gate and up projection into one kernel.
# The naive implementation of FFN layer is: ``FFN(x) = down_proj(silu(gate(x)) * up(x))``
# We could combine the ``gate`` and ``up`` projection into one kernel for better performance.
# The optimized implementation is:
#
# .. code-block:: python
#
# concat_x = gate_up(x)
# gate_x, up_x = split(concat_x, 2, axis=-1)
# FFN(x) = down_proj(silu(gate_x) * up_x)
#
class LlamaFFN(nn.Module):
def __init__(self, config: LlamaConfig):
super().__init__()
self.gate_up_proj = nn.Linear(
in_features=config.hidden_size,
out_features=2 * config.intermediate_size,
bias=False,
)
self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
def forward(self, x: Tensor):
concat_x1_x2 = self.gate_up_proj(x)
x1, x2 = op.split(concat_x1_x2, 2, axis=-1)
return self.down_proj(op.silu(x1) * x2)
######################################################################
# Then we define the self-attention layer. The self-attention layer consists of three parts:
#
# - QKV projection: The QKV projection converts the input hidden states to the query, key, and
# value tensors.
# - Attention: The attention layer computes the attention scores and applies the softmax
# operation.
# - Output projection: The output projection converts the attention output to the hidden states.
#
# We perform optimizations on the different parts of the self-attention layer:
#
# - QKV projection: We leverage the horizontal fusion on QKV projection and fuse them into one
# kernel.
# - Attention: We leverage the horizontal fusion on attention and fuse the QKV projection and
class LlamaAttention(nn.Module): # pylint: disable=too-many-instance-attributes
def __init__(self, config: LlamaConfig):
self.head_dim = config.head_dim
self.num_q_heads = config.num_attention_heads
self.num_kv_heads = config.num_key_value_heads
# horizontal fusion on QKV projection
self.qkv_proj = nn.Linear(
in_features=config.hidden_size,
out_features=(self.num_q_heads + 2 * self.num_kv_heads) * self.head_dim,
bias=False,
)
self.o_proj = nn.Linear(self.num_q_heads * self.head_dim, config.hidden_size, bias=False)
def forward(self, hidden_states: Tensor, paged_kv_cache: PagedKVCache, layer_id: int):
d, h_q, h_kv = self.head_dim, self.num_q_heads, self.num_kv_heads
b, s, _ = hidden_states.shape
# QKV Projection
qkv = self.qkv_proj(hidden_states)
qkv = op.reshape(qkv, (b, s, h_q + h_kv + h_kv, d))
# Attention
output = op.reshape(
paged_kv_cache.attention_with_fused_qkv(
layer_id, qkv, self.num_q_heads, sm_scale=self.head_dim**-0.5
),
(b, s, h_q * d),
)
# Output Projection
return self.o_proj(output)
######################################################################
# Finally, we define the model architecture with FFN and self-attention layers.
class LlamaDecoderLayer(nn.Module):
def __init__(self, config: LlamaConfig):
rms_norm_eps = config.rms_norm_eps
self.self_attn = LlamaAttention(config)
self.mlp = LlamaFFN(config)
self.input_layernorm = nn.RMSNorm(config.hidden_size, -1, rms_norm_eps, bias=False)
self.post_attention_layernorm = nn.RMSNorm(config.hidden_size, -1, rms_norm_eps, bias=False)
def forward(self, hidden_states: Tensor, paged_kv_cache: PagedKVCache, layer_id: int):
hidden_states += self.self_attn(
self.input_layernorm(hidden_states), paged_kv_cache, layer_id
)
hidden_states += self.mlp(self.post_attention_layernorm(hidden_states))
return hidden_states
class LlamaModel(nn.Module):
def __init__(self, config: LlamaConfig):
assert config.hidden_size % config.num_attention_heads == 0
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
self.layers = nn.ModuleList(
[LlamaDecoderLayer(config) for _ in range(config.num_hidden_layers)]
)
self.norm = nn.RMSNorm(config.hidden_size, -1, config.rms_norm_eps, bias=False)
def forward(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
hidden_states = input_embed
for layer_id, layer in enumerate(self.layers):
hidden_states = layer(hidden_states, paged_kv_cache, layer_id)
hidden_states = self.norm(hidden_states)
return hidden_states
class LlamaForCausalLM(nn.Module):
def __init__(self, config: LlamaConfig):
self.model = LlamaModel(config)
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
self.num_hidden_layers = config.num_hidden_layers
self.num_attention_heads = config.num_attention_heads
self.num_key_value_heads = config.num_key_value_heads
self.head_dim = config.head_dim
self.hidden_size = config.hidden_size
self.vocab_size = config.vocab_size
self.rope_theta = config.rope_theta
self.dtype = "float32"
def to(self, dtype: str | None = None):
super().to(dtype=dtype)
if dtype is not None:
self.dtype = dtype
def embed(self, input_ids: Tensor):
return self.model.embed_tokens(input_ids)
def get_logits(self, hidden_states: Tensor):
logits = self.lm_head(hidden_states)
if logits.dtype != "float32":
logits = logits.astype("float32")
return logits
def prefill(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
def _index(x: te.Tensor): # x[:-1,:]
b, s, d = x.shape
return te.compute((b, 1, d), lambda i, _, k: x[i, s - 1, k], name="index")
hidden_states = self.model(input_embed, paged_kv_cache)
hidden_states = op.tensor_expr_op(_index, name_hint="index", args=[hidden_states])
logits = self.get_logits(hidden_states)
return logits, paged_kv_cache
def decode(self, input_embed: Tensor, paged_kv_cache: PagedKVCache):
hidden_states = self.model(input_embed, paged_kv_cache)
logits = self.get_logits(hidden_states)
return logits, paged_kv_cache
def create_tir_paged_kv_cache(
self,
max_batch_size: tirx.Var,
max_total_seq_len: tirx.Var,
prefill_chunk_size: tirx.Var,
page_size: tirx.Var,
) -> PagedKVCache:
return TIRPagedKVCache(
attn_kind="mha",
max_batch_size=max_batch_size,
max_total_seq_len=max_total_seq_len,
prefill_chunk_size=prefill_chunk_size,
page_size=page_size,
support_sliding_window=0,
layer_partition=relax.ShapeExpr([0, self.num_hidden_layers]),
num_hidden_layers=self.num_hidden_layers,
num_attention_heads=self.num_attention_heads,
num_key_value_heads=self.num_key_value_heads,
qk_head_dim=self.head_dim,
v_head_dim=self.head_dim,
mla_original_qk_head_dim=0,
mla_original_v_head_dim=0,
rope_mode=RopeMode.NORMAL,
rope_scale=1,
rope_theta=self.rope_theta,
rope_scaling={},
rope_ext_factors=tirx.IntImm("int64", 0),
rotary_dim=self.head_dim,
dtype=self.dtype,
target=target,
enable_disaggregation=False,
)
def get_default_spec(self):
mod_spec = {
"embed": {
"input_ids": nn.spec.Tensor(["seq_len"], "int32"),
"$": {
"param_mode": "packed",
"effect_mode": "none",
},
},
"prefill": {
"input_embed": nn.spec.Tensor([1, "seq_len", self.hidden_size], self.dtype),
"paged_kv_cache": nn.spec.Object(object_type=PagedKVCache),
"$": {
"param_mode": "packed",
"effect_mode": "none",
},
},
"decode": {
"input_embed": nn.spec.Tensor([1, 1, self.hidden_size], self.dtype),
"paged_kv_cache": nn.spec.Object(object_type=PagedKVCache),
"$": {
"param_mode": "packed",
"effect_mode": "none",
},
},
"create_tir_paged_kv_cache": {
"max_batch_size": int,
"max_total_seq_len": int,
"prefill_chunk_size": int,
"page_size": int,
"$": {
"param_mode": "none",
"effect_mode": "none",
},
},
}
return nn.spec.ModuleSpec.from_raw(mod_spec, self)
######################################################################
# Export the model to Relax IRModule
# ----------------------------------
# After defining the model architecture, we can export the model to the Relax IRModule.
# For demonstration, we only show the part of the model architecture. and parameters.
model_config = LlamaConfig()
model = LlamaForCausalLM(model_config)
model.to("float16")
mod, named_params = model.export_tvm(spec=model.get_default_spec())
prefill_str = mod["prefill"].script()
print(*prefill_str.split("\n")[3:20], sep="\n") # Only show the first 10 lines for demonstration
print(" ...")
print("\nParameters:")
pprint(named_params[:5]) # Only show the first 5 parameters for demonstration
######################################################################
# Define Optimization Pipeline
# ----------------------------
# We define a series of optimization passes to optimize the model. The optimization pipeline
# is designed specifically for the LLMs.
@register_pipeline("opt_llm")
def _pipeline( # pylint: disable=too-many-arguments
ext_mods: list[nn.ExternModule] | None = None,
):
ext_mods = ext_mods or []
@tvm.transform.module_pass(opt_level=0)
def _pipeline(mod: tvm.ir.IRModule, _ctx: tvm.transform.PassContext) -> tvm.ir.IRModule:
seq = tvm.transform.Sequential(
[
# Phase 1. Passes on high-level operator graph
# We can enable cublas for further optimization
relax.transform.FuseTransposeMatmul(),
# Phase 2. Lowering to TIR, inherited TVM Relax's official "zero" pipeline
relax.transform.LegalizeOps(),
relax.transform.AnnotateTIROpPattern(),
relax.transform.FoldConstant(),
relax.transform.FuseOps(),
relax.transform.FuseTIR(),
# Phase 3. Passes on TIR
relax.transform.DeadCodeElimination(),
# Phase 4. Low-level Optimizations
dlight.ApplyDefaultSchedule(
dlight.gpu.Matmul(),
dlight.gpu.GEMV(),
dlight.gpu.Reduction(),
dlight.gpu.GeneralReduction(),
dlight.gpu.Fallback(),
),
# Phase 5. Lowering to VM bytecode
relax.transform.RewriteDataflowReshape(),
relax.transform.ToNonDataflow(),
relax.transform.RemovePurityChecking(),
relax.transform.CallTIRRewrite(),
relax.transform.StaticPlanBlockMemory(),
relax.transform.RewriteCUDAGraph(),
relax.transform.LowerAllocTensor(),
relax.transform.KillAfterLastUse(),
relax.transform.LowerRuntimeBuiltin(),
relax.transform.VMShapeLower(),
relax.transform.AttachGlobalSymbol(),
relax.transform.AttachExternModules(ext_mods),
]
)
mod = seq(mod)
return mod
return _pipeline
with target:
ex = tvm.compile(mod, target, relax_pipeline=relax.get_pipeline("opt_llm"))
vm = relax.VirtualMachine(ex, dev)
######################################################################
# Prepare the model weights
# -------------------------
# We load the pre-trained weights from Hugging Face and prepare the model weights.
# The pre-trained weights are stored in the Hugging Face format. We need to load the weights
# and prepare the model parameters.
#
# .. note::
#
# Note that we won't execute the following code in this tutorial because the pre-trained weights
# are not available in the CI environment.
#
IS_IN_CI = os.getenv("CI", "") == "true"
HF_WEIGHT_PATH = None
# HF_WEIGHT_PATH = Path("/path/to/TinyLlama-1.1B-Chat-v1.0/")
if not IS_IN_CI:
import numpy as np
import safetensors.torch
import torch
if HF_WEIGHT_PATH is None or not HF_WEIGHT_PATH.exists():
raise ValueError("Please set the HF_WEIGHT_PATH to the path of the pre-trained weights.")
# Torch format weights
param_dict = safetensors.torch.load_file(HF_WEIGHT_PATH / "model.safetensors", device="cpu")
# Numpy format weights
param_dict = {
k: v.half().numpy() if v.dtype == torch.bfloat16 else v.numpy()
for k, v in param_dict.items()
}
named_params = dict(named_params)
for i in range(model_config.num_hidden_layers):
# Add QKV in self attention
attn = f"model.layers.{i}.self_attn"
param_dict[f"{attn}.qkv_proj.weight"] = np.concatenate(
[
param_dict.pop(f"{attn}.q_proj.weight"), # Pop the old parameters to save memory
param_dict.pop(f"{attn}.k_proj.weight"),
param_dict.pop(f"{attn}.v_proj.weight"),
],
axis=0,
)
# Add gates in MLP
mlp = f"model.layers.{i}.mlp"
param_dict[f"{mlp}.gate_up_proj.weight"] = np.concatenate(
[
param_dict.pop(f"{mlp}.gate_proj.weight"),
param_dict.pop(f"{mlp}.up_proj.weight"),
],
axis=0,
)
# Convert params into ndarray
params = [
tvm.runtime.tensor(param_dict[k].astype("float16"), device=dev) for k in named_params.keys()
]
######################################################################
# Deploy the compiled model
# -------------------------
# After the model and weights are ready, we can deploy the compiled model on the target device.
# The language models inference includes two steps: prefill and decode. The prefill step is
# used to process the input tokens and store the KVCache. The decode step is used to generate
# the token until the end token is generated.
######################################################################
# Tokenization
# ~~~~~~~~~~~~
# The first step is to tokenize the input prompt and embed the tokens into the hidden states.
# The tokenization and embedding are the same as the original model. We use the HF tokenizer
# to tokenize the input prompt and embed the tokens into the hidden states.
# Note that different models require different tokenization and prompt format, please refer to
# the model documentation for the correct tokenization and prompt format.
if not IS_IN_CI:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(HF_WEIGHT_PATH)
messages = [
{"role": "user", "content": "What's your name?"},
]
prompt = tokenizer.apply_chat_template(messages)
input_len = len(prompt)
# Load prompt tokens into TVM ndarray on the target device
tokens = tvm.runtime.tensor(np.array(prompt).astype("int32"), device=dev)
######################################################################
# Create the KVCache
# ~~~~~~~~~~~~~~~~~~
# Before starting the inference, we need to create the KVCache. The KVCache is used to store the
# key and value tensors for the attention layer. Apache TVM provides a PagedKVCache to store the
# key and value tensors. We create the PagedKVCache with the specified parameters.
if not IS_IN_CI:
kv_cache = vm["create_tir_paged_kv_cache"](
Shape([1]), # max_batch_size=1
Shape([2048]), # max_total_seq_len=2048
Shape([2048]), # prefill_chunk_size=2048
Shape([16]), # page_size=16
)
######################################################################
# Embedding
# ~~~~~~~~~
# The next step is to embed the tokens into the hidden states. We use the `embed` function
# compiled in the Relax IRModule to embed the tokens into the hidden states.
nd_view_func = tvm.get_global_func("vm.builtin.reshape")
def embed(tokens, params):
_embed = vm["embed"](tokens, params)
# Reshape hidden from [seq_len, hidden_size] to [1, seq_len, hidden_size]
_embed = nd_view_func(_embed, Shape([1, _embed.shape[0], _embed.shape[1]]))
return _embed
######################################################################
# Prefill
# ~~~~~~~
# Before running the forward pass, we first get some help functions for preparation.
add_sequence_func = tvm.get_global_func("vm.builtin.kv_state_add_sequence")
begin_forward_func = tvm.get_global_func("vm.builtin.kv_state_begin_forward")
end_forward_func = tvm.get_global_func("vm.builtin.kv_state_end_forward")
######################################################################
# As we are creating a new sequence, we need to call `add_sequence_func` to initialize
# the request. Additionally, we need to call `begin_forward_func` to start the forward pass,
# and `end_forward_func` to end the forward pass.
if not IS_IN_CI:
seq_id = 0
add_sequence_func(kv_cache, seq_id)
hidden_states = embed(tokens, params)
begin_forward_func(kv_cache, Shape([seq_id]), Shape([input_len]))
logits, kv_cache = vm["prefill"](hidden_states, kv_cache, params)
end_forward_func(kv_cache)
######################################################################
# Now we have the output logits from the prefill step. The logits are used to generate the token
# via sampling. Let's sample the token from the logits.
#
# In this tutorial, we simplify the sampling process and pick the token with the highest
# probability. In practice, we should sample the token based on the probability distribution.
# Also, to make the tutorial concise, we execute the sample process on CPU.
def sample_token(logits):
logits_np = logits.numpy()
return np.argmax(logits_np)
if not IS_IN_CI:
last_token = sample_token(logits)
output_tokens = [last_token]
######################################################################
# Decode
# ~~~~~~
# After the prefill step, we can start the decode step. The decode step is used to generate the
# token until the end token is generated. We use the `decode` function compiled in the Relax
# IRModule to generate the token.
if not IS_IN_CI:
print("The generated token:")
while last_token != tokenizer.eos_token_id:
tokens = tvm.runtime.tensor(np.array([last_token]).astype("int32"), device=dev)
hidden_states = embed(tokens, params)
begin_forward_func(kv_cache, Shape([seq_id]), Shape([1]))
logits, kv_cache = vm["decode"](hidden_states, kv_cache, params)
end_forward_func(kv_cache)
last_token = sample_token(logits)
output_tokens.append(last_token)
print(tokenizer.decode(output_tokens))
+96
View File
@@ -0,0 +1,96 @@
.. 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.
Apache TVM Documentation
========================
Welcome to the documentation for Apache TVM, a deep learning compiler that
enables access to high-performance machine learning anywhere for everyone.
TVM's diverse community of hardware vendors, compiler engineers and ML
researchers work together to build a unified, programmable software stack, that
enriches the entire ML technology ecosystem and make it accessible to the wider
ML community. TVM empowers users to leverage community-driven ML-based
optimizations to push the limits and amplify the reach of their research and
development, which in turn raises the collective performance of all ML, while
driving its costs down.
.. toctree::
:maxdepth: 1
:caption: Getting Started
get_started/overview
install/index
get_started/tutorials/quick_start
get_started/tutorials/ir_module
errors
.. toctree::
:maxdepth: 1
:caption: How To
how_to/tutorials/import_model
how_to/tutorials/e2e_opt_model
how_to/tutorials/customize_opt
how_to/tutorials/optimize_llm
how_to/tutorials/cross_compilation_and_rpc
how_to/tutorials/export_and_load_executable
how_to/tutorials/mix_python_and_tvm_with_pymodule
how_to/tutorials/bring_your_own_codegen
.. The Deep Dive content is comprehensive
.. we maintain a ``maxdepth`` of 2 to display more information on the main page.
.. toctree::
:maxdepth: 2
:caption: Deep Dive
arch/index
deep_dive/tensor_ir/index
deep_dive/relax/index
.. toctree::
:maxdepth: 2
:caption: TIRx
tirx/overview
tirx/install
tirx/native_basics
tirx/layout
tirx/tile_primitives
tirx/arch/index
tirx/api/index
.. toctree::
:maxdepth: 1
:caption: API Reference
reference/api/python/index
reference/api/links
.. toctree::
:maxdepth: 1
:caption: About
contribute/index
reference/publications
reference/security
.. toctree::
:maxdepth: 1
:caption: Index
genindex
+83
View File
@@ -0,0 +1,83 @@
.. 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.
.. _docker-images:
Docker Images
=============
We provide docker utility scripts to help developers to setup development environment.
They are also helpful run through TVM demo and tutorials.
We need `docker <https://docs.docker.com/engine/installation/>`_ and
`NVIDIA Container Toolkit <https://github.com/NVIDIA/nvidia-container-toolkit>`_
if we want to use CUDA.
Get a tvm source distribution or clone the GitHub repo to get the auxiliary scripts
.. code:: bash
git clone --recursive https://github.com/apache/tvm tvm
We can then use the following command to launch a docker image.
.. code:: bash
/path/to/tvm/docker/bash.sh <image-name>
Here the image-name can be a local docker image name, e.g. ``tvm.ci_cpu`` after you have done the local build.
This auxiliary script does the following things:
- Mount current directory to ``/workspace``
- Switch user to be the same user that calls the ``bash.sh`` (so you can read/write host system)
- Use the host-side network on Linux. Use the bridge network and expose port 8888 on macOS,
because host networking driver isn't supported. (so you can use ``jupyter notebook``)
Then you can start a Jupyter notebook by typing
.. code:: bash
jupyter notebook
You might see an error ``OSError: [Errno 99] Cannot assign requested address`` when starting
a Jupyter notebook on macOS. You can change the binding IP address by
.. code:: bash
jupyter notebook --ip=0.0.0.0
Note that on macOS, because ``bash.sh`` uses the Docker bridge network, Jupyter will be reportedly running
at an URL like ``http://{container_hostname}:8888/?token=...``. You should replace the ``container_hostname``
with ``localhost`` when pasting it into browser.
Docker Source
-------------
Check out `the docker source <https://github.com/apache/tvm/tree/main/docker>`_ if you are interested in
building your own docker images.
Run the following command to build the docker image.
.. code:: bash
/path/to/tvm/docker/build.sh <image-name>
You can find some un-official third party pre-built images at `<https://hub.docker.com/r/tlcpack/>`_.
These images are used for test purposes and are NOT of the ASF release.
+356
View File
@@ -0,0 +1,356 @@
.. 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.
.. _install-from-source:
Install from Source
===================
This page gives instructions on how to build and install the TVM package from source.
.. contents:: Table of Contents
:local:
:depth: 2
.. _install-dependencies:
Step 1. Install Dependencies
----------------------------
Apache TVM requires the following dependencies:
- CMake (>= 3.24.0)
- LLVM (recommended >= 15)
- Git
- A recent C++ compiler supporting C++ 20, at the minimum
- GCC 10
- Clang 10
- Apple Clang 14
- Visual Studio 2022
Optional dependencies that use newer C++20 standard library facilities, such
as ``std::format``, may require a newer standard library (for example GCC 13
or newer on Linux).
- Python (>= 3.10)
- (Optional) Conda (Strongly Recommended)
System Dependencies (Non-Conda)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are not using Conda, TVM requires several system libraries. On Ubuntu/Debian systems, install them with:
.. code:: bash
sudo apt update
sudo apt install zlib1g-dev libxml2-dev
For other operating systems, please refer to your package manager documentation.
The easiest way to manage dependency is via conda, which maintains a set of toolchains
including LLVM across platforms. To create the environment of those build dependencies,
one may simply use:
.. code:: bash
# make sure to start with a fresh environment
conda env remove -n tvm-build-venv
# create the conda environment with build dependency
conda create -n tvm-build-venv -c conda-forge \
"llvmdev>=15" \
"cmake>=3.24" \
git \
python=3.11
# enter the build environment
conda activate tvm-build-venv
.. note::
**For Frontend Contributors (TFLite):** If you plan to run or contribute to the frontend tests (e.g., ``test_frontend_tflite.py``), you must install ``tensorflow==2.19.0``.
.. code:: bash
pip install tensorflow==2.19.0
Python 3.11 is supported.
Step 2. Get Source from GitHub
------------------------------
You can also choose to clone the source repo from GitHub.
.. code:: bash
git clone --recursive https://github.com/apache/tvm tvm
.. note::
It's important to use the ``--recursive`` flag when cloning the TVM repository, which will
automatically clone the submodules. If you forget to use this flag, you can manually clone the submodules
by running ``git submodule update --init --recursive`` in the root directory of the TVM repository.
Step 3. Configure and Build
---------------------------
Create a build directory and run CMake to configure the build. The following example shows how to build
.. code:: bash
cd tvm
rm -rf build && mkdir build && cd build
# Specify the build configuration via CMake options
cp ../cmake/config.cmake .
We want to specifically tweak the following flags by appending them to the end of the configuration file:
.. code:: bash
# controls default compilation flags (Candidates: Release, Debug, RelWithDebInfo)
echo "set(CMAKE_BUILD_TYPE RelWithDebInfo)" >> config.cmake
# LLVM is a must dependency for compiler end
echo "set(USE_LLVM \"llvm-config --ignore-libllvm --link-static\")" >> config.cmake
echo "set(HIDE_PRIVATE_SYMBOLS ON)" >> config.cmake
# GPU SDKs, turn on if needed
echo "set(USE_CUDA OFF)" >> config.cmake
echo "set(USE_METAL OFF)" >> config.cmake
echo "set(USE_VULKAN OFF)" >> config.cmake
echo "set(USE_OPENCL OFF)" >> config.cmake
# cuBLAS, cuDNN, cutlass support, turn on if needed
echo "set(USE_CUBLAS OFF)" >> config.cmake
echo "set(USE_CUDNN OFF)" >> config.cmake
echo "set(USE_CUTLASS OFF)" >> config.cmake
.. note::
``HIDE_PRIVATE_SYMBOLS`` is a configuration option that enables the ``-fvisibility=hidden`` flag.
This flag helps prevent potential symbol conflicts between TVM and PyTorch. These conflicts arise due to
the frameworks shipping LLVMs of different versions.
`CMAKE_BUILD_TYPE <https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html>`_ controls default compilation flag:
- ``Debug`` sets ``-O0 -g``
- ``RelWithDebInfo`` sets ``-O2 -g -DNDEBUG`` (recommended)
- ``Release`` sets ``-O3 -DNDEBUG``
Once ``config.cmake`` is edited accordingly, kick off build with the commands below:
.. code-block:: bash
cmake .. && cmake --build . --parallel $(nproc)
.. note::
``nproc`` may not be available on all systems, please replace it with the number of cores on your system
A success build should produce ``libtvm`` and ``libtvm_runtime`` under ``build/`` directory.
Apache TVM relies on the tvm-ffi package to support its python bindings.
Therefore, after we finish the build, we need to install the tvm-ffi package.
.. code-block:: bash
cd 3rdparty/tvm-ffi; pip install .; cd ..
Leaving the build environment ``tvm-build-venv``, there are two ways to install the successful build into your environment:
- Install via environment variable
.. code-block:: bash
export TVM_HOME=/path-to-tvm
pip install --target=$TVM_HOME/python $TVM_HOME/3rdparty/tvm-ffi
export PYTHONPATH=$TVM_HOME/python:$PYTHONPATH
- Install via pip local project
.. code-block:: bash
conda activate your-own-env
conda install python # make sure python is installed
export TVM_LIBRARY_PATH=/path-to-tvm/build
pip install -e /path-to-tvm
Step 4. Validate Installation
-----------------------------
Using a compiler infrastructure with multiple language bindings could be error-prone.
Therefore, it is highly recommended to validate Apache TVM installation before use.
**Step 1. Locate TVM Python package.** The following command can help confirm that TVM is properly installed as a python package and provide the location of the TVM python package:
.. code-block:: bash
>>> python -c "import tvm; print(tvm.__file__)"
/some-path/lib/python3.11/site-packages/tvm/__init__.py
**Step 2. Confirm which TVM library is used.** When maintaining multiple build or installation of TVM, it becomes important to double check if the python package is using the proper ``libtvm`` with the following command:
.. code-block:: bash
>>> python -c "import tvm; print(tvm.base._LOADED_LIBS['tvm_runtime'])"
<CDLL '/some-path/lib/python3.11/site-packages/tvm/libtvm_runtime.dylib', handle 95ada510 at 0x1030e4e50>
**Step 3. Reflect TVM build option.** Sometimes when downstream application fails, it could likely be some mistakes with a wrong TVM commit, or wrong build flags. To find it out, the following commands will be helpful:
.. code-block:: bash
>>> python -c "import tvm; print('\n'.join(f'{k}: {v}' for k, v in tvm.support.libinfo().items()))"
... # Omitted less relevant options
GIT_COMMIT_HASH: 4f6289590252a1cf45a4dc37bce55a25043b8338
HIDE_PRIVATE_SYMBOLS: ON
USE_LLVM: llvm-config --link-static
LLVM_VERSION: 15.0.7
USE_VULKAN: OFF
USE_CUDA: OFF
CUDA_VERSION: NOT-FOUND
USE_OPENCL: OFF
USE_METAL: ON
USE_ROCM: OFF
**Step 4. Check device detection.** Sometimes it could be helpful to understand if TVM could detect your device at all with the following commands:
.. code-block:: bash
>>> python -c "import tvm; print(tvm.metal().exist)"
True # or False
>>> python -c "import tvm; print(tvm.cuda().exist)"
False # or True
>>> python -c "import tvm; print(tvm.vulkan().exist)"
False # or True
Please note that the commands above verify the presence of an actual device on the local machine for the TVM runtime (not the compiler) to execute properly. However, TVM compiler can perform compilation tasks without requiring a physical device. As long as the necessary toolchain, such as NVCC, is available, TVM supports cross-compilation even in the absence of an actual device.
Step 5. Extra Python Dependencies
---------------------------------
Building from source does not ensure the installation of all necessary Python dependencies.
Python Dependencies
~~~~~~~~~~~~~~~~~~~
The following commands can be used to install the extra Python dependencies:
* Necessary dependencies:
.. code:: bash
pip3 install numpy cython
* If you want to use RPC Tracker
.. code:: bash
pip3 install tornado
* If you want to use auto-tuning module
.. code:: bash
pip3 install tornado psutil 'xgboost>=1.1.0' cloudpickle
Windows-Specific Build Notes
----------------------------
If you're building TVM on Windows, note these platform-specific considerations:
Path Conventions
~~~~~~~~~~~~~~~~
- Use forward slashes (``/``) in Python/CMake paths, not Windows backslashes
- Example: ``python cmake/config.cmake`` not ``python cmake\\config.cmake``
Advanced Build Configuration
----------------------------
Ccache
~~~~~~
On supported platforms, the `Ccache compiler wrapper <https://ccache.dev/>`_ may be helpful for
reducing TVM's build time, especially when building with `cutlass <https://github.com/NVIDIA/cutlass>`_.
There are several ways to enable CCache in TVM builds:
- Leave ``USE_CCACHE=AUTO`` in ``build/config.cmake``. CCache will be used if it is found.
- Ccache's Masquerade mode. This is typically enabled during the Ccache installation process.
To have TVM use Ccache in masquerade, simply specify the appropriate C/C++ compiler
paths when configuring TVM's build system. For example:
``cmake -DCMAKE_CXX_COMPILER=/usr/lib/ccache/c++ ...``.
- Ccache as CMake's C++ compiler prefix. When configuring TVM's build system,
set the CMake variable ``CMAKE_CXX_COMPILER_LAUNCHER`` to an appropriate value.
E.g. ``cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ...``.
Building on Windows
~~~~~~~~~~~~~~~~~~~
TVM support build via MSVC using CMake. You will need to obtain a visual studio compiler.
The minimum required VS version is **Visual Studio Enterprise 2019** (NOTE: we test
against GitHub Actions' `windows-latest Runner <https://github.com/actions/runner-images>`_, so see that page for full details.
We recommend following :ref:`install-dependencies` to obtain necessary dependencies and
get an activated tvm-build environment. Then you can run the following command to build
.. code:: bash
mkdir build
cd build
cmake ..
cd ..
The above command generates the solution file under the build directory.
You can then run the following command to build
.. code:: bash
cmake --build build --config Release -- /m
CUDA Configuration
..................
For CUDA support on Windows:
.. code-block:: batch
set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8
set PATH=%CUDA_PATH%\bin;%PATH%
cmake .. -DUSE_CUDA=ON
CMake & Compiler Setup
......................
- Specify generator: ``cmake -G "Visual Studio 16 2019" -A x64 ..``
- Ensure Python is in PATH or specify: ``-DPython_EXECUTABLE=C:\Python311\python.exe``
Building ROCm support
~~~~~~~~~~~~~~~~~~~~~
Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
- Set ``set(USE_ROCM ON)``, set ROCM_PATH to the correct path.
- You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
- Install LLVM (>= 15), and LLD, make sure ``ld.lld`` is available via command line.
.. _install-from-source-cpp-tests:
Enable C++ Tests
~~~~~~~~~~~~~~~~
We use `Google Test <https://github.com/google/googletest>`_ to drive the C++
tests in TVM. The easiest way to install GTest is from source.
.. code:: bash
git clone https://github.com/google/googletest
cd googletest
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=ON ..
make
sudo make install
After installing GTest, the C++ tests can be built and started with ``./tests/scripts/task_cpp_unittest.sh``, or built via CMake with ``-DUSE_GTEST=ON`` and then running ``./build/cpptest``.
+41
View File
@@ -0,0 +1,41 @@
.. 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.
.. _installation:
Installing TVM
==============
.. toctree::
:maxdepth: 1
pypi
from_source
docker
For most Python users, the quickest way to get started is to
:ref:`install the Apache TVM wheel from PyPI <install-from-pypi>`.
Visit the :ref:`install TVM from source <install-from-source>` page to install
TVM from the source code. Installing from source gives you the maximum
flexibility to configure the build effectively from the official source
releases. If you are interested in deploying to mobile or embedded devices,
you do not need to install the entire TVM stack on your device. Instead, you
only need the runtime.
If you would like to quickly try out TVM or run some demo and tutorials, you
can :ref:`install from Docker <docker-images>`.
+51
View File
@@ -0,0 +1,51 @@
.. 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.
.. _install-from-pypi:
Install from PyPI
=================
For most Python users, the quickest way to get started is to install the Apache
TVM wheel from PyPI:
.. code-block:: bash
pip install apache-tvm
This installs the Python package, including modules such as ``tvm.tirx``, and
is suitable for trying tutorials that do not require a custom build.
CUDA environments
-----------------
Some CUDA workflows use NVIDIA's Python CUDA bindings for runtime compilation.
Install the CUDA extra in the same environment as TVM when you need this path:
.. code-block:: bash
pip install "apache-tvm[cuda]"
This extra installs Python-side CUDA bindings only. It does not make the PyPI
wheel a CUDA-enabled TVM build, and it does not install NVIDIA drivers or a CUDA
toolkit. If you need CUDA support in TVM itself, build TVM from source with
``USE_CUDA=ON``.
For more details on installing the TIRx compiler and optional kernel library,
visit the :doc:`TIRx installation </tirx/install>` page. If you need to
customize TVM's build configuration, visit the
:ref:`install TVM from source <install-from-source>` page instead.
+26
View File
@@ -0,0 +1,26 @@
.. 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.
Other APIs
==========
This page contains links to API references that are built with different doc
build system.
* `C++ doxygen API <doxygen/index.html>`_
* `Typescript typedoc API <typedoc/index.html>`_
* `Java Javadoc API <javadoc/index.html>`_
+22
View File
@@ -0,0 +1,22 @@
.. 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.
tvm.arith
---------
.. automodule:: tvm.arith
:members:
:imported-members:
+123
View File
@@ -0,0 +1,123 @@
.. 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.
tvm.contrib
-----------
.. automodule:: tvm.contrib
tvm.contrib.cblas
~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.cblas
:members:
tvm.contrib.coreml_runtime
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.coreml_runtime
:members:
tvm.contrib.cublas
~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.cublas
:members:
tvm.contrib.cublaslt
~~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.cublaslt
:members:
tvm.contrib.cudnn
~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.cudnn
:members:
tvm.contrib.dlpack
~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.dlpack
:members:
tvm.contrib.dnnl
~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.dnnl
:members:
tvm.contrib.download
~~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.download
:members:
tvm.contrib.hipblas
~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.hipblas
:members:
tvm.contrib.mkl
~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.mkl
:members:
tvm.contrib.nnpack
~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.nnpack
:members:
tvm.contrib.pickle_memoize
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.pickle_memoize
:members:
tvm.contrib.random
~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.random
:members:
tvm.contrib.thrust
~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.thrust
:members:
tvm.contrib.tvmjs
~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.tvmjs
:members:
tvm.contrib.cutlass
~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.cutlass
:members:
:imported-members:
tvm.contrib.hexagon
~~~~~~~~~~~~~~~~~~~
.. automodule:: tvm.contrib.hexagon
:members:
:imported-members:
+22
View File
@@ -0,0 +1,22 @@
.. 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.
tvm.driver
----------
.. automodule:: tvm.driver
.. autofunction:: tvm.compile
+23
View File
@@ -0,0 +1,23 @@
.. 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.
tvm.error
---------
.. automodule:: tvm.error
:members:
:imported-members:
:autosummary:
+22
View File
@@ -0,0 +1,22 @@
.. 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.
tvm.exec
--------
.. automodule:: tvm.exec
:members:
:imported-members:
+100
View File
@@ -0,0 +1,100 @@
.. 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.
Python API
==========
.. toctree::
:maxdepth: 1
:caption: tvm
error
ir
arith
instrument
transform
target
driver
testing
exec
support
.. toctree::
:maxdepth: 1
:caption: tvm.runtime
runtime/runtime
runtime/vm
runtime/disco
.. toctree::
:maxdepth: 1
:caption: tvm.relax
relax/relax
relax/analysis
relax/backend
relax/block_builder
relax/distributed
relax/frontend
relax/op
relax/testing
relax/training
relax/transform
relax/dpl
.. toctree::
:maxdepth: 1
:caption: tvm.s_tir
s_tir/analysis
s_tir/schedule
s_tir/transform
s_tir/dlight
s_tir/backend
s_tir/tensor_intrin
.. toctree::
:maxdepth: 1
:caption: tvm.script
script/script
script/parser
script/ir_builder
script/printer
.. toctree::
:maxdepth: 1
:caption: tvm.te
te
topi
.. toctree::
:maxdepth: 1
:caption: tvm.s_tir.meta_schedule
meta_schedule
.. toctree::
:maxdepth: 1
:caption: Misc
rpc
contrib
+23
View File
@@ -0,0 +1,23 @@
.. 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.
tvm.ir.instrument
------------------
.. automodule:: tvm.ir.instrument
:members:
:imported-members:
:exclude-members: Path
+24
View File
@@ -0,0 +1,24 @@
.. 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.
tvm.ir
------
.. automodule:: tvm.ir
:members:
:imported-members:
:exclude-members: Expr
:autosummary:
+146
View File
@@ -0,0 +1,146 @@
.. 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.
tvm.s_tir.meta_schedule
------------------------
tvm.s_tir.meta_schedule
***********************
.. automodule:: tvm.s_tir.meta_schedule
:members:
:imported-members:
:autosummary:
tvm.s_tir.meta_schedule.arg_info
********************************
.. automodule:: tvm.s_tir.meta_schedule.arg_info
:members:
:noindex:
tvm.s_tir.meta_schedule.builder
*******************************
.. automodule:: tvm.s_tir.meta_schedule.builder
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.cost_model
**********************************
.. automodule:: tvm.s_tir.meta_schedule.cost_model
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.database
********************************
.. automodule:: tvm.s_tir.meta_schedule.database
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.feature_extractor
*****************************************
.. automodule:: tvm.s_tir.meta_schedule.feature_extractor
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.measure_callback
****************************************
.. automodule:: tvm.s_tir.meta_schedule.measure_callback
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.mutator
*******************************
.. automodule:: tvm.s_tir.meta_schedule.mutator
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.postproc
********************************
.. automodule:: tvm.s_tir.meta_schedule.postproc
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.runner
******************************
.. automodule:: tvm.s_tir.meta_schedule.runner
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.schedule_rule
*************************************
.. automodule:: tvm.s_tir.meta_schedule.schedule_rule
:members:
:noindex:
tvm.s_tir.meta_schedule.search_strategy
***************************************
.. automodule:: tvm.s_tir.meta_schedule.search_strategy
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.space_generator
***************************************
.. automodule:: tvm.s_tir.meta_schedule.space_generator
:members:
:noindex:
tvm.s_tir.meta_schedule.task_scheduler
**************************************
.. automodule:: tvm.s_tir.meta_schedule.task_scheduler
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.tir_integration
***************************************
.. automodule:: tvm.s_tir.meta_schedule.tir_integration
:members:
:noindex:
tvm.s_tir.meta_schedule.relax_integration
*****************************************
.. automodule:: tvm.s_tir.meta_schedule.relax_integration
:members:
:noindex:
tvm.s_tir.meta_schedule.trace_apply
************************************
.. automodule:: tvm.s_tir.meta_schedule.trace_apply
:members:
:noindex:
tvm.s_tir.meta_schedule.schedule
********************************
.. automodule:: tvm.s_tir.meta_schedule.schedule
:members:
:imported-members:
:noindex:
tvm.s_tir.meta_schedule.post_optimization
*****************************************
.. automodule:: tvm.s_tir.meta_schedule.post_optimization
:members:
:imported-members:
:noindex:
@@ -0,0 +1,22 @@
.. 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.
tvm.relax.analysis
------------------
.. automodule:: tvm.relax.analysis
:members:
:imported-members:
@@ -0,0 +1,67 @@
.. 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.
tvm.relax.backend
-----------------
tvm.relax.backend
*****************
.. automodule:: tvm.relax.backend
:members:
:imported-members:
tvm.relax.backend.cuda
**********************
.. automodule:: tvm.relax.backend.cuda
:members:
:no-index:
tvm.relax.backend.rocm
**********************
.. automodule:: tvm.relax.backend.rocm
:members:
:no-index:
tvm.relax.backend.metal
***********************
.. automodule:: tvm.relax.backend.metal
:members:
:no-index:
tvm.relax.backend.adreno
************************
.. automodule:: tvm.relax.backend.adreno
:members:
:no-index:
tvm.relax.backend.gpu_generic
*****************************
.. automodule:: tvm.relax.backend.gpu_generic
:members:
:no-index:
tvm.relax.backend.cpu_generic
*****************************
.. automodule:: tvm.relax.backend.cpu_generic
:members:
:no-index:
tvm.relax.backend.contrib
*************************
.. automodule:: tvm.relax.backend.contrib
:members:
:no-index:
@@ -0,0 +1,21 @@
.. 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.
tvm.relax.block_builder
-----------------------
.. automodule:: tvm.relax.block_builder
:members:
@@ -0,0 +1,28 @@
.. 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.
tvm.relax.distributed
---------------------
.. automodule:: tvm.relax.distributed
:members:
:imported-members:
tvm.relax.distributed.transform
*******************************
.. automodule:: tvm.relax.distributed.transform
:members:
:no-index:
+21
View File
@@ -0,0 +1,21 @@
.. 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.
tvm.relax.dpl
-------------
.. automodule:: tvm.relax.dpl
:members:
@@ -0,0 +1,60 @@
.. 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.
tvm.relax.frontend
------------------
.. automodule:: tvm.relax.frontend
:members:
:imported-members:
tvm.relax.frontend.nn
*********************
.. automodule:: tvm.relax.frontend.nn
:members:
:imported-members:
:exclude-members: BlockBuilder
:noindex:
tvm.relax.frontend.nn.llm
*************************
.. automodule:: tvm.relax.frontend.nn.llm
:members:
:no-index:
tvm.relax.frontend.onnx
***********************
.. automodule:: tvm.relax.frontend.onnx
:members:
:imported-members:
tvm.relax.frontend.stablehlo
****************************
.. automodule:: tvm.relax.frontend.stablehlo
:members:
:imported-members:
tvm.relax.frontend.tflite
*************************
.. automodule:: tvm.relax.frontend.tflite
:members:
:imported-members:
tvm.relax.frontend.torch
************************
.. automodule:: tvm.relax.frontend.torch
:members:
:imported-members:
+95
View File
@@ -0,0 +1,95 @@
.. 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.
tvm.relax.op
------------
tvm.relax.op
************
.. automodule:: tvm.relax.op
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.nn
***************
.. automodule:: tvm.relax.op.nn
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.builtin
********************
.. automodule:: tvm.relax.op.builtin
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.ccl
****************
.. automodule:: tvm.relax.op.ccl
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.distributed
************************
.. automodule:: tvm.relax.op.distributed
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.grad
*****************
.. automodule:: tvm.relax.op.grad
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.image
******************
.. automodule:: tvm.relax.op.image
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.memory
*******************
.. automodule:: tvm.relax.op.memory
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.vision
*******************
.. automodule:: tvm.relax.op.vision
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.vm
***************
.. automodule:: tvm.relax.op.vm
:members:
:imported-members:
:exclude-members: Expr
tvm.relax.op.op_attrs
*********************
.. automodule:: tvm.relax.op.op_attrs
:members:
:exclude-members: Attrs
+23
View File
@@ -0,0 +1,23 @@
.. 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.
tvm.relax
---------
.. automodule:: tvm.relax
:members:
:imported-members:
:exclude-members: BlockBuilder, Call, Span, GlobalVar, SourceName, TupleType, Type, FuncType
@@ -0,0 +1,22 @@
.. 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.
tvm.relax.testing
-----------------
.. automodule:: tvm.relax.testing
:members:
:no-index:
@@ -0,0 +1,22 @@
.. 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.
tvm.relax.training
------------------
.. automodule:: tvm.relax.training
:members:
:imported-members:
@@ -0,0 +1,131 @@
.. 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.
.. _api-relax-transformation:
tvm.relax.transform
-------------------
.. automodule:: tvm.relax.transform
:members:
:imported-members:
tvm.relax.transform.legalize_ops
*********************************
.. automodule:: tvm.relax.transform.legalize_ops
:members:
tvm.relax.transform.legalize_ops.binary
========================================
.. automodule:: tvm.relax.transform.legalize_ops.binary
:members:
:no-index:
tvm.relax.transform.legalize_ops.ccl
=====================================
.. automodule:: tvm.relax.transform.legalize_ops.ccl
:members:
:no-index:
tvm.relax.transform.legalize_ops.create
========================================
.. automodule:: tvm.relax.transform.legalize_ops.create
:members:
:no-index:
tvm.relax.transform.legalize_ops.datatype
==========================================
.. automodule:: tvm.relax.transform.legalize_ops.datatype
:members:
:no-index:
tvm.relax.transform.legalize_ops.distributed
==============================================
.. automodule:: tvm.relax.transform.legalize_ops.distributed
:members:
:no-index:
tvm.relax.transform.legalize_ops.grad
======================================
.. automodule:: tvm.relax.transform.legalize_ops.grad
:members:
:no-index:
tvm.relax.transform.legalize_ops.image
=======================================
.. automodule:: tvm.relax.transform.legalize_ops.image
:members:
:no-index:
tvm.relax.transform.legalize_ops.index
=======================================
.. automodule:: tvm.relax.transform.legalize_ops.index
:members:
:no-index:
tvm.relax.transform.legalize_ops.linear_algebra
================================================
.. automodule:: tvm.relax.transform.legalize_ops.linear_algebra
:members:
:no-index:
tvm.relax.transform.legalize_ops.manipulate
=============================================
.. automodule:: tvm.relax.transform.legalize_ops.manipulate
:members:
:no-index:
tvm.relax.transform.legalize_ops.nn
====================================
.. automodule:: tvm.relax.transform.legalize_ops.nn
:members:
:no-index:
tvm.relax.transform.legalize_ops.qdq
=====================================
.. automodule:: tvm.relax.transform.legalize_ops.qdq
:members:
:no-index:
tvm.relax.transform.legalize_ops.search
========================================
.. automodule:: tvm.relax.transform.legalize_ops.search
:members:
:no-index:
tvm.relax.transform.legalize_ops.statistical
==============================================
.. automodule:: tvm.relax.transform.legalize_ops.statistical
:members:
:no-index:
tvm.relax.transform.legalize_ops.unary
=======================================
.. automodule:: tvm.relax.transform.legalize_ops.unary
:members:
:no-index:
tvm.relax.transform.legalize_ops.vision
========================================
.. automodule:: tvm.relax.transform.legalize_ops.vision
:members:
:no-index:
tvm.relax.transform.legalize_ops.adreno
========================================
.. automodule:: tvm.relax.transform.legalize_ops.adreno
:members:
:no-index:
+23
View File
@@ -0,0 +1,23 @@
.. 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.
tvm.rpc
-------
.. automodule:: tvm.rpc
:members:
:imported-members:
:autosummary:
@@ -0,0 +1,22 @@
.. 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.
tvm.runtime.disco
-----------------
.. automodule:: tvm.runtime.disco
:members:
:imported-members:
@@ -0,0 +1,21 @@
.. 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.
tvm.runtime
-----------
.. automodule:: tvm.runtime
:members:
+21
View File
@@ -0,0 +1,21 @@
.. 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.
tvm.runtime.vm
--------------------
.. automodule:: tvm.runtime.vm
:members:
@@ -0,0 +1,21 @@
.. 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.
tvm.s_tir.analysis
------------------
.. automodule:: tvm.s_tir.analysis
:members:
@@ -0,0 +1,42 @@
.. 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.
tvm.s_tir.backend
-----------------
tvm.s_tir.backend
*****************
.. automodule:: tvm.s_tir.backend
:members:
:imported-members:
tvm.s_tir.backend.adreno
************************
.. automodule:: tvm.s_tir.backend.adreno
:members:
:imported-members:
tvm.s_tir.backend.adreno.pipeline
*********************************
.. automodule:: tvm.s_tir.backend.adreno.pipeline
:members:
tvm.s_tir.backend.adreno.transform
***********************************
.. automodule:: tvm.s_tir.backend.adreno.transform
:members:
:imported-members:
@@ -0,0 +1,66 @@
.. 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.
tvm.s_tir.dlight
----------------
tvm.s_tir.dlight
****************
.. automodule:: tvm.s_tir.dlight
:members:
:imported-members:
tvm.s_tir.dlight.gpu
********************
.. automodule:: tvm.s_tir.dlight.gpu
:members:
:imported-members:
:noindex:
tvm.s_tir.dlight.adreno
***********************
.. automodule:: tvm.s_tir.dlight.adreno
:members:
:imported-members:
:noindex:
tvm.s_tir.dlight.cpu
********************
.. automodule:: tvm.s_tir.dlight.cpu
:members:
:imported-members:
:noindex:
tvm.s_tir.dlight.analysis
*************************
.. automodule:: tvm.s_tir.dlight.analysis
:members:
:imported-members:
:noindex:
tvm.s_tir.dlight.base
*********************
.. automodule:: tvm.s_tir.dlight.base
:members:
:noindex:
tvm.s_tir.dlight.benchmark
**************************
.. automodule:: tvm.s_tir.dlight.benchmark
:members:
:imported-members:
:noindex:
@@ -0,0 +1,22 @@
.. 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.
tvm.s_tir.schedule
------------------
.. automodule:: tvm.s_tir.schedule
:members:
:imported-members:
@@ -0,0 +1,73 @@
.. 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.
tvm.s_tir.tensor_intrin
-----------------------
tvm.s_tir.tensor_intrin
***********************
.. automodule:: tvm.s_tir.tensor_intrin
:members:
:imported-members:
tvm.s_tir.tensor_intrin.cuda
****************************
.. automodule:: tvm.s_tir.tensor_intrin.cuda
:members:
:noindex:
tvm.s_tir.tensor_intrin.arm_cpu
*******************************
.. automodule:: tvm.s_tir.tensor_intrin.arm_cpu
:members:
:noindex:
tvm.s_tir.tensor_intrin.x86
****************************
.. automodule:: tvm.s_tir.tensor_intrin.x86
:members:
:noindex:
tvm.s_tir.tensor_intrin.rocm
*****************************
.. automodule:: tvm.s_tir.tensor_intrin.rocm
:members:
:noindex:
tvm.s_tir.tensor_intrin.metal
*****************************
.. automodule:: tvm.s_tir.tensor_intrin.metal
:members:
:noindex:
tvm.s_tir.tensor_intrin.hexagon
*******************************
.. automodule:: tvm.s_tir.tensor_intrin.hexagon
:members:
:noindex:
tvm.s_tir.tensor_intrin.riscv_cpu
*********************************
.. automodule:: tvm.s_tir.tensor_intrin.riscv_cpu
:members:
:noindex:
tvm.s_tir.tensor_intrin.dot_product_common
******************************************
.. automodule:: tvm.s_tir.tensor_intrin.dot_product_common
:members:
:noindex:
@@ -0,0 +1,23 @@
.. 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.
tvm.s_tir.transform
-------------------
.. automodule:: tvm.s_tir.transform
:members:
:imported-members:
:no-index:

Some files were not shown because too many files have changed in this diff Show More