【TVM 教程】如何使用 TVM Pass Infra

TVM Relay与Pass TVM Relay与Pass 本文介绍TVM的Relay,如何基于Relay构建一个Conv+BN+ReLU的小网络, TVM中的Pass的工作机制,并较为详细的介绍了RemoveUnusedFunctions,ToBasicBlockNormalForm,EliminateCommonSubexpr三种Pass。其中Relay部分的详细介绍大部分引用自官方文档: https://tvm.apache.org/docs/tutorials/get_started/introduction.html。 TVM 阅读详情

Apache TVM 是一个深度的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →https://tvm.hyper.ai/

作者:Zhi Chen

随着 Relay/tir 中优化 Pass 数的增加,手动执行并维护它们的依赖关系变得难以处理。因此我们引入了一个基础架构来管理优化 Pass,并使其适用于 TVM 堆栈中 IR 的不同层。

Relay/tir 程序的优化可以在各种粒度上应用,即分别使用 tvm.relay.transform.FunctionPass/ tvm.tir.transform.PrimFuncPass 和 tvm.transform.ModulePass 的功能级和模块级,或者用户可以依靠 tvm.transform.Sequential 在 Relay/tir 程序上应用一系列 Pass,其中 Pass 之间的依赖关系可以通过 pass infra 来解决。有关这些 Pass 类型的更多详细信息,参阅 Pass Infrastructure。

本教程主要演示开发者如何使用 pass infra 进行某种优化,并为 Relay 程序创建优化 Pass。同样的方法也可以用于 tir。

import numpy as np
import tvm
from tvm import te
import tvm.relay as relay

创建 Relay 程序示例​

首先为教程创建一个简单的 Relay 程序,该程序用于本教程中示例的各种优化。同样,用户可以编写 tir 原始函数并应用 tir pass。

def example():
    shape = (1, 64, 54, 54)
    c_data = np.empty(shape).astype("float32")
    c = relay.const(c_data)
    weight = relay.var("weight", shape=(64, 64, 3, 3))
    x = relay.var("x", relay.TensorType((1, 64, 56, 56), "float32"))
    conv = relay.nn.conv2d(x, weight)
    y = relay.add(c, c)
    y = relay.multiply(y, relay.const(2, "float32"))
    y = relay.add(conv, y)
    z = relay.add(y, c)
    z1 = relay.add(y, c)
    z2 = relay.add(z, z1)
    return relay.Function([x, weight], z2)

优化程序​

接下来优化程序,Relay 具有许多优化功能,选择其中一部分应用到这个示例程序中。

有多种方法可以优化 Relay 程序。下面将逐一讲解。

手动应用优化 Pass

# 优化函数。
f = example()
mod = tvm.IRModule.from_expr(f)

# 现在可以在模块上应用常量折叠。
# fold_const 是一个不带任何参数的回调函数。
fold_const = relay.transform.FoldConstant()
# 然后,在给定的模块上调用 pass。注意,常数
# folding pass 在函数级别工作。话虽如此,每个
# 模块中的函数将应用优化。用户无需迭代
# 通过各个函数手动应用此 pass。
mod = fold_const(mod)
# 从更新后的程序中可以看到常量被折叠了。
print(mod)

输出结果:

  "target_host parameter is going to be deprecated. "
def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %3 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

可以以类似的方式应用更多优化。例如,可以消除 z 和 z1 使用的常用表达式。

mod = relay.transform.EliminateCommonSubexpr()(mod)
print(mod)

输出结果:

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

融合也是参数化的,例如,opt level 0 将不允许算子融合在一起。用户可以通过 fuse_opt_level 来启用它。

mod = relay.transform.FuseOps(fuse_opt_level=0)(mod)

# 可以观察到优化后的模块包含的函数只有
# 一个单一的原始操作。
print(mod)

输出结果:

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = fn (%p03: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %p12: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    nn.conv2d(%p03, %p12, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 56, 56), float32], Tensor[(64, 64, 3, 3), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %1 = %0(%x, %weight) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = fn (%p02: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p11: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    add(%p02, %p11) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %3 = %2(%1, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %4 = fn (%p01: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p1: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    add(%p01, %p1) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %5 = %4(%3, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %6 = fn (%p0: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    add(%p0, %p0) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %6(%5) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

使用 Sequential 应用一系列 Pass​

如上所述应用 Pass 实际上很乏味,并且可能需要用户更好地了解它们之间的依赖关系。例如,融合目前在 let 绑定上效果不佳。因此,如果在融合之前应用了 relay.transform.ToANormalForm(),将无法融合可融合的算子,因为此过程会为每个表达式生成 let 绑定以规范 Relay 程序。

因此,Relay 提供了 tvm.transform.Sequential,使得开发者能够更容易地处理这些问题。他们通过显式指定每个 pass 所需的 pass,然后将它们打包为一个整体来实现。

例如,使用下面的 sequential 来应用相同的 pass。tvm.transform.Sequential 类似于 torch.nn.sequential 和 mxnet.gluon.block。例如,torch.nn.sequential 包含一系列 PyTorch 模块,这些模块将会用来构建网络。它侧重于网络层。相反,我们的 pass infra 中的 tvm.transform.Sequential 用于优化 pass。

# 通过 :py:class:`tvm.transform.Sequential` 执行一些传递
f = example()
mod = tvm.IRModule.from_expr(f)
# Glob 感兴趣的 passes。
seq = tvm.transform.Sequential(
    [
        relay.transform.FoldConstant(),
        relay.transform.EliminateCommonSubexpr(),
        relay.transform.FuseOps(fuse_opt_level=2),
    ]
)
mod1 = seq(mod)
print(mod1)

输出结果:

/workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.
  "target_host parameter is going to be deprecated. "
def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %4 = fn (%p0: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %p1: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */, %p2: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p3: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    %0 = nn.conv2d(%p0, %p1, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %1 = add(%0, %p2) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %2 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %3 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 56, 56), float32], Tensor[(64, 64, 3, 3), float32], Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %4(%x, %weight, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

从改造后的 Relay 程序中,可以看到仍然有两个相同的加法运算。这是因为 EliminateCommonSubexpr 并未实际执行。原因是在 tvm.transform.Sequential 下,只有优化级别小于或等于 2 的 pass 才会默认执行。pass infra 为用户提供了一个配置界面来自定义想要执行的优化级别。

with tvm.transform.PassContext(opt_level=3):
    mod2 = seq(mod)
print(mod2)

输出结果:

/workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.
  "target_host parameter is going to be deprecated. "
def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %3 = fn (%p0: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %p1: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */, %p2: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p3: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    %0 = nn.conv2d(%p0, %p1, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %1 = add(%0, %p2) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %2 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 56, 56), float32], Tensor[(64, 64, 3, 3), float32], Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %3(%x, %weight, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

现在可以看到两个相同的加法只保留一个。

用户可以使用 disabled_pass 配置选择性地禁用某些 pass,这与 Clang 和 GCC 等通用编译器使用的 -fno-xxx 选项类似。例如,可以禁用以下 EliminateCommonSubexpr,打印的模块将再次显示两个相同的加法操作。

with tvm.transform.PassContext(opt_level=3, disabled_pass=["EliminateCommonSubexpr"]):
    mod3 = seq(mod)
print(mod3)

输出结果:

/workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.
  "target_host parameter is going to be deprecated. "
def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %4 = fn (%p0: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %p1: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */, %p2: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p3: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    %0 = nn.conv2d(%p0, %p1, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %1 = add(%0, %p2) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %2 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %3 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 56, 56), float32], Tensor[(64, 64, 3, 3), float32], Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %4(%x, %weight, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

使用 Python 装饰器实现 Pass​

下一个示例说明如何使用 Python 装饰器通过 pass infra 来自定义优化 pipeline。此功能极大地简化了pass 的实现。例如,用户可以简单地定义一个装饰类来进行函数级优化,如下例所示。 transform_function 包装了一个类,用 c 的倍数替换所有常量。稍后,访问给定模块中的每个函数,并且调用自定义 pass 时,函数中的每个常量都将被替换。

@relay.transform.function_pass(opt_level=1)
class CustomPipeline:
    """Simple test function to replace one argument to another."""

    def __init__(self, multiplier):
        self.multiplier = multiplier

    # 这个函数可以定义一个pass。
    def transform_function(self, func, mod, ctx):
        obj = self

        class ReplaceConstant(tvm.relay.ExprMutator):
            def visit_constant(self, c):
                return relay.multiply(obj.multiplier, c)

        return ReplaceConstant().visit(func)

f = example()
mod = tvm.IRModule.from_expr(f)
custom_pass = CustomPipeline(multiplier=relay.const(3, "float32"))
assert custom_pass.info.name == "CustomPipeline"
mod3 = custom_pass(mod)
print(mod3)

输出结果:

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = multiply(3f /* ty=float32 */, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, %0) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = multiply(3f /* ty=float32 */, 2f /* ty=float32 */) /* ty=float32 */;
  %3 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %4 = multiply(%1, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %5 = add(%3, %4) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %6 = add(%5, %0) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %7 = add(%5, %0) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%6, %7) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

调试 Pass​

TVM 为用户提供了即插即用式调试 Pass,通过特殊 pass (PrintIR) 转储整个模块的 IR,在完成某个 pass 后打印 IR。pass 序列示例的轻微修改版本如下所示,用于启用 IR 转储以进行 FoldConstant 优化。

f = example()
mod = tvm.IRModule.from_expr(f)
seq = tvm.transform.Sequential(
    [
        relay.transform.FoldConstant(),
        tvm.transform.PrintIR(),
        relay.transform.EliminateCommonSubexpr(),
        relay.transform.FuseOps(),
    ]
)

通过在 FoldConstant 之后插入 PrintIR pass,pass infra 将在 FoldConstant 完成时转储模块 IR。用户可以在任何想要调试的 pass 之后插入这个 pass 来查看优化效果。

此外,还有一个更灵活的调试机制,可以实现一个 PassInstrument 类来执行任意代码,不仅在每次传递之前和/或之后,而且在进入/退出 PassContext 时也可以。有关详细信息,参阅 Pass Instrument。

这里使用 tvm.instrument.pass_instrument 装饰器来实现一个 PassInsturment 类,在每次执行之前打印 IR:

@tvm.instrument.pass_instrument
class PrintIR:
    """仅在 pass 执行之前打印 pass 的名称,IR。"""

    def run_before_pass(self, mod, info):
        print("Running pass: {}", info)
        print(mod)

with tvm.transform.PassContext(opt_level=3, instruments=[PrintIR()]):
    with tvm.target.Target("llvm"):
        # 执行优化。
        mod = seq(mod)
print(mod)

print("done")

输出结果:

Running pass: {} The meta data of the pass - pass name: sequential, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32], %weight: Tensor[(64, 64, 3, 3), float32]) {
  %0 = add(meta[relay.Constant][0], meta[relay.Constant][0]);
  %1 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]);
  %2 = multiply(%0, 2f);
  %3 = add(%1, %2);
  %4 = add(%3, meta[relay.Constant][0]);
  %5 = add(%3, meta[relay.Constant][0]);
  add(%4, %5)
}

Running pass: {} The meta data of the pass - pass name: FoldConstant, opt_level: 2, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32], %weight: Tensor[(64, 64, 3, 3), float32]) {
  %0 = add(meta[relay.Constant][0], meta[relay.Constant][0]);
  %1 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]);
  %2 = multiply(%0, 2f);
  %3 = add(%1, %2);
  %4 = add(%3, meta[relay.Constant][0]);
  %5 = add(%3, meta[relay.Constant][0]);
  add(%4, %5)
}

/workspace/python/tvm/driver/build_module.py:268: UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.
  "target_host parameter is going to be deprecated. "
Running pass: {} The meta data of the pass - pass name: InferType, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32], %weight: Tensor[(64, 64, 3, 3), float32]) {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]);
  %1 = add(%0, meta[relay.Constant][0]);
  %2 = add(%1, meta[relay.Constant][1]);
  %3 = add(%1, meta[relay.Constant][1]);
  add(%2, %3)
}

Running pass: {} The meta data of the pass - pass name: PrintIR, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %3 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: InferType, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %3 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: EliminateCommonSubexpr, opt_level: 3, required passes: [
InferType, ]

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %3 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %3) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: InferType, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: InferType, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: FuseOps, opt_level: 0, required passes: [
InferType, ]

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %0 = nn.conv2d(%x, %weight, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %1 = add(%0, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  %2 = add(%1, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */;
  add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

Running pass: {} The meta data of the pass - pass name: InferType, opt_level: 0, required passes: []

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %3 = fn (%p0: Tensor[(1, 64, 56, 56), float32], %p1: Tensor[(64, 64, 3, 3), float32], %p2: Tensor[(1, 64, 54, 54), float32], %p3: Tensor[(1, 64, 54, 54), float32], Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    %0 = nn.conv2d(%p0, %p1, padding=[0, 0, 0, 0]);
    %1 = add(%0, %p2);
    %2 = add(%1, %p3);
    add(%2, %2)
  };
  %3(%x, %weight, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */)
}

def @main(%x: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %weight: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */) -> Tensor[(1, 64, 54, 54), float32] {
  %3 = fn (%p0: Tensor[(1, 64, 56, 56), float32] /* ty=Tensor[(1, 64, 56, 56), float32] */, %p1: Tensor[(64, 64, 3, 3), float32] /* ty=Tensor[(64, 64, 3, 3), float32] */, %p2: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, %p3: Tensor[(1, 64, 54, 54), float32] /* ty=Tensor[(1, 64, 54, 54), float32] */, Primitive=1) -> Tensor[(1, 64, 54, 54), float32] {
    %0 = nn.conv2d(%p0, %p1, padding=[0, 0, 0, 0]) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %1 = add(%0, %p2) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    %2 = add(%1, %p3) /* ty=Tensor[(1, 64, 54, 54), float32] */;
    add(%2, %2) /* ty=Tensor[(1, 64, 54, 54), float32] */
  } /* ty=fn (Tensor[(1, 64, 56, 56), float32], Tensor[(64, 64, 3, 3), float32], Tensor[(1, 64, 54, 54), float32], Tensor[(1, 64, 54, 54), float32]) -> Tensor[(1, 64, 54, 54), float32] */;
  %3(%x, %weight, meta[relay.Constant][0] /* ty=Tensor[(1, 64, 54, 54), float32] */, meta[relay.Constant][1] /* ty=Tensor[(1, 64, 54, 54), float32] */) /* ty=Tensor[(1, 64, 54, 54), float32] */
}

done

总结​

本教程介绍了如何使用 pass infra 更方便地在 TVM 中编写和调用 pass。还讨论了调用 pass 的不同方式。使用 tvm.transform.Sequential 可以在很大程度上帮助用户简化处理多个优化过程及其依赖关系的工作。此外,还提供了一个示例来说明如何使用 PrintIR 和跟踪来调试 pass。

下载 Python 源代码:use_pass_infra.py

下载 Jupyter Notebook:use_pass_infra.ipynb

用TVM在硬件平台上部署深度学习工作负载的端到端 IR 堆栈 用TVM在硬件平台上部署深度学习工作负载的端到端 IR 堆栈 深度学习已变得无处不在,不可或缺。这场革命的一部分是由可扩展的深度学习系统推动的,如滕索弗洛、MXNet、咖啡和皮托奇。大多数现有系统针对范围狭窄的服务器级 GPU 进行了优化,需要在其它平台,如移动电话、物联网设备和专用加速器(FPGA、ASIC)上部署大量精力。随着深度学习框架和硬件后端数量的增加,建议建立一个统一的中间表示 (IR) 堆栈,以缩小以生产力为中心的深度学习框架与面向性能或效率的硬件后端之间的差距。 TVM 是一个新颖的框架, 阅读详情

相关推荐

TVM(一):简介与安装

简介 TVM是一个用于深度学习系统的编译器堆栈。 它旨在缩小以生产力为中心的深度学习框架与以性能和效率为中心的硬件后端之间的差距。TVM与深度学习框架合作,为不同的后端提供端到端编译。 换句话说,TVM就是一种将深度学习工作负载部署到硬件的端到端IR(中间表示)堆栈。可以表述为一种把深度学习模型分发到各种硬件设备上的、端到端的解决方案。 GitHub:https://github.com/dmlc...

u014448054的博客 1万+

【TVM 教程】使用 TVM 部署框架预量化模型 - 第 3 部分(TFLite)

更多 TVM 中文文档可访问 →此教程介绍如何量化 TFLite 计算图,并通过 TVM 编译和执行。有关使用 TFLite 量化模型的更多详细信息,参阅。TFLite 模型下载。开始前,先安装 TensorFlow 和 TFLite 包。执行命令检查 TFLite 包是否安装成功。

HyperAI超神经 1580

Jitsi中的Prosody(谐音:破扫敌)介绍

凭借其灵活的插件系统、强大的安全性和出色的性能,Prosody 成为许多企业和开发者构建定制化通信解决方案的首选。在 Jitsi 的多方视频会议场景中,Prosody 不仅处理基本的信令任务,还需要与其他组件(如 Jitsi Videobridge)协作,确保视频流的正确分发和控制。Prosody 提供了丰富的插件系统,允许开发者通过模块扩展其功能。Prosody 就是一个实现 XMPP 协议的服务器,它支持 XMPP 协议的标准功能,如消息传递、群组聊天、传送状态信息等,并提供了高效、可扩展的服务。

江左散人的专栏 1166

TVM Pass概述

use pass infra Design and Architecture Pass又称transform,每一个transform要么把现有程序转换并优化为一个等价的程序,要么把程序lower到下层。relay/transform包括很多优化模型用的pass,包括常量折叠(constant folding)和死代码删除(dead-code elimination)等。 这里拿tests/python/relay/test_pass_fold_constant.py里的单测作为例子: import num

Eurypterid的博客 1352

MMDetection框架入门教程(完全版)

  网上MMDetection的教程看似有很多,但感觉都不成系统,看完一圈下来还是不知道MMDetection要怎么用。这里还是推荐直接跟着官方教程,结合源码学习MMDetection,相关链接汇总如下: 官方教程 - MMCV 官方教程 - MMDetection 官方教程 - 不得不知的 MMDetection 学习路线(个人经验版) 西安交大课件 - mmdetection教程(使用篇)   本文会介绍如何在MMDetection中从头开始搭建一套属于自己的算法。前几篇博客算是本人学习过...

Sual 10万+

论文学习:Towards Automated Infographic Design:Deep Learning-based Auto-generation of Extensible Timeline

title: “Towards Automated Infographic Design:Deep Learning-based Auto-generation of Extensible Timeline” tags: [“论文评述”, “报告”] date: 2019-08-22 author: 叶慧 mathjax: true 论文:Towards Automated Infograph...

xiaoyehahaha的博客 731

使用TVM优化深度学习GPU算子:深度卷积实例

以下内容翻译自:Optimize Deep Learning GPU Operators with TVM: A Depthwise Convolution Example 高效的深度学习算子是深度学习系统的核心。通常这些算子很难优化,并且需要高性能计算专家的努力。TVM,端到端张量IR/DSL堆栈,使得这项任务更容易。 这个博客教你如何在TVM的帮助下编写高性能GPU运算核心。我们使用深度卷...

yiran103的专栏 1万+

Axure RP 9在Windows 11环境下的高效安装与配置指南

本文提供Axure RP 9在Windows 11系统下的详细安装步骤与配置指南。针对Win11安全特性,从获取官方安装资源、调整系统权限,到定制化安装路径、精准汉化及性能优化,系统性地解决了安装闪退、汉化失败、运行卡顿等常见问题,确保用户获得稳定流畅的原型设计体验。

mango的博客 480

tvm安装与使用

$git clone https://github.com/apache/incubator-tvm.gittvm&& cd tvm $git submodule update --init --recursive $mkdir build && cd build && cp ../cmake/config.cmake . $补齐llvm等库的路径,决定是否使用CUDA等。 $cmake ../ && make -j4 ...

u011994454的博客 2314

破盾实录|Vueling航空阿卡迈防护逆向:从503挑战到Cookie生成的全流程拆解

摘要:文章分享了作者在爬取西班牙伏林航空(Vueling)机票数据时,遭遇阿卡迈(Akamai)防护系统的实战经历。起初简单的请求被拦截返回503错误,分析发现阿卡迈通过混淆JS代码实施"挑战-验证"机制,需模拟真实浏览器环境才能获取有效Cookie。作者详细拆解了逆向过程:从抓包分析JS挑战流程,到破解代码混淆和控制流平坦化,最终用Python+Pyppeteer成功模拟浏览器特征,突破防护获取数据。整个过程涉及JS反混淆、环境检测绕过和Cookie生成验证等关键技术点,为类似防护系统

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1643

AUTOSAR知识点:PDUR(PDU Router)基础知识

通过定义路由表和实现相应的路由和缓冲逻辑,PDUR能够确保PDU在不同ECU之间的可靠传输。在上述示例中,我们定义了一个简单的PDUR路由表,其中列出了源ECU和目标ECU之间的映射关系。当收到来自源ECU的PDU时,PDUR模块会根据路由表查找目标ECU,并将PDU传输到相应的目标ECU。PDUR的主要功能是将接收到的PDU从一个ECU路由到另一个ECU,并确保数据的正确传输。需要注意的是,上述示例是一个简化的实现,实际的PDUR模块可能包含更复杂的功能和逻辑。

EcmFsharp的博客 1157

【从零开始学深度学习编译器】八,TVM的算符融合以及如何使用TVM Pass Infra自定义Pass

对于一张有向图(可以有环)我们规定一个起点, 从点到图上另一个点可能存在很多条路径(下面将到简写为)。如果对于的任意一条路径中都存在一个点, 那么我们称点为的支配点(也可以称作是的必经点), 注意点不讨论支配点. 下面用表示离点最近的支配点。对于原图上除外每一个点, 从向建一条边, 最后我们可以得到一个以为根的树. 这个树我们就叫它"支配树"。这篇文章介绍了TVMPass Infra的一些实践例子,然后介绍了支配树和算符融合Pass。

生活需要深度 868

【异常】安装hermes-agent提示Git克隆仓库网络中断 fetch-pack: unexpected disconnect while reading sideband packet

本文分析了Git克隆仓库时出现网络中断错误的常见原因及解决方案。报错表现为连接被远程主机关闭、管道破裂或数据包意外终止,通常由网络不稳定、仓库过大或配置不当引起。提供了5种优先级排序的解决方法:1)使用浅克隆减少数据量;2)优化Git配置参数;3)切换网络或配置代理;4)检查SSH密钥设置;5)改用HTTPS方式克隆。特别推荐先尝试「浅克隆+HTTPS」组合方案快速验证,若问题持续再根据具体报错深入排查。文中包含详细的命令示例和配置说明,适合不同网络环境下的Git用户参考。

奶爸的编程之路,也就一周冷个三天 1418

TVM Compiler中文教程:使用TVM编写可调模板和使用自动调优器

文章目录使用TVM编写可调模板和使用自动调优器加载依赖库第一步:定义搜索空间参数化调度使用更好的空间定义API第二步:搜索空间在TVM中使用自动调优器开始调优 使用TVM编写可调模板和使用自动调优器 这是TVM中auto-tuning模块的入门教程。 auto-tuning分两个步骤:第一步定义搜索空间;第二步是运行搜索算法来探索这个空间。在本教程中,你可以了解如何在TVM中执行这两个步骤。下面通...

Mars_WH的专栏 5314

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

TVM Compiler中文教程:TVM使用内联和数学函数

文章目录TVM使用内联和数学函数直接声明外部数学函数调用统一内联函数调用内部Lowering规则总结 TVM使用内联和数学函数 TVM支持基础算术运算操作,但是在很多情况下我们需要更复杂的內建函数。例如exp指数函数。 这些內建函数取决于目标系统,在不同的平台可能有不同的名字。这个教程中,我们将学习调用目标特定的內建函数,和怎么能够通过TVM内联API统一接口。 from __future__ i...

Mars_WH的专栏 2267

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

初探TVM--使用Tensor Engine来编写算子

TVM的tensor expression

shaojie_wang的博客 1697

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

TVM Compiler中文教程:TVM.Relay使用外部库

文章目录TVM.Relay中使用外部库创建一个简单的网络使用cuda后端构建和运行卷积层使用cuDNN实现验证结果结论 TVM.Relay中使用外部库 这篇教程介绍怎么在Relay中使用cuDNN、cuBlas这样的外部库。 Relay在内部使用TVM生成特定目标的代码。例如,使用cuda作为后端,TVM为用户提供的网络生成所有层的cuda kernel代码。但是有时,将设备厂商提供的外部库合并到...

Mars_WH的专栏 3913

TVM Compiler中文教程:TVM使用张量化Tensorize利用硬件内联函数

文章目录TVM使用Tensorize利用硬件内联函数定义矩阵乘法调度矩阵乘法定义GEMV张量内联函数 TVM使用Tensorize利用硬件内联函数 这篇教程是关于在TVM中如何执行张量化的介绍。 通过使用调度原语tensorize,人们可以用相应的内联函数替换计算单元,从而可以轻松利用手工制作的微内核函数,和扩展TVM来支持新的硬件架构。 本教程的目的是展示tensorize的功能和用法,而不是提...

Mars_WH的专栏 4753

深度学习编译中间件之NNVM(四)TVM设计理念与开发者指南

参考文档 http://docs.tvmlang.org/dev/index.html TVM Design and Developer Guide 本文档为官方指导手册的中文翻译版本,主要涉及到TVM的设计理念和开发者指南,适用于计划深入掌握TVM深度定制开发技术的开发者。 TVM运行时系统 TVM支持多种编程语言下的编译器堆栈开发和部署,针对本文档我们主要会介绍TVM运行时的关...

sanallen的专栏 1万+

如何使用TVM Pass Relay

如何使用TVM Pass红外线 随着Relay / tir中优化遍数的增加,执行并手动维护其依赖关系变得很棘手。引入了一个基础结构来管理优化过程,将其应用于TVM堆栈中IR的不同层。 Relay / tir程序的优化可以以各种粒度应用,分别使用tvm.relay.transform.FunctionPass/ tvm.tir.transform.PrimFuncPass和的功能级别和模块级别tvm.transform.ModulePass 。用户可以依靠在tvm.transform.Sequential r

吴建明wujianming_110117 679

TVM中PackedFun机制学习笔记

为了便于Python和C++混合编程,TVM使用了统一的PackedFunc机制。PackedFunc可以将C++中的各类函数打包成统一的函数接口,并自动导出到Python模块中进行调用,并且也支持从Python中注册一个函数,并伪装成PackedFunc在C++和Python中调用。 python调用c++ c++中 c++中需要调用的函数 #include <tvm/runtime/packed_func.h> void MyAdd(TVMArgs args, TVMRetValue* r

qq_42784403的博客 756

TVM-安装说明

本文主要讲述TVM的源码安装流程,详细流程建议参考官方文档Install from Source。本文主要简述安装流程,以及安装过程遇到的问题。

zgh 1806

利用TVM优化ARM GPU上的移动深度学习

原文地址:https://tvm.ai/2018/01/16/opt-mali-gpu.html 随着深度学习的巨大成功,对移动设备部署深度神经网络的需求正在迅速增长。与我们在桌面平台上的工作类似,在移动设备中使用GPU可以同时提高推理速度和能效。但是,大多数现有的深度学习框架都不能很好地支持移动GPU。难点在于移动GPU架构和桌面GPU架构之间的差异。这意味着在移动GPU上进行优...

u012101561的博客 6545

TVM Compiler中文教程:TVM如何优化CPU GEMM(矩阵乘法)

文章目录TVM如何优化CPU GEMM(矩阵乘法)准备和基线Opt1:分块Opt2:向量化Opt3:循环排布permuteOpt4:数组打包Opt5:为块写cacheOpt6:并行 TVM如何优化CPU GEMM(矩阵乘法) TVM提供抽象接口,允许用户分别描述算法和算法的实施组织(所谓的调度Schedule)。通常,写高性能调度的算法时,会破坏算法的可读性和模块性。此外,尝试各种看似有用的调度是...

Mars_WH的专栏 5588
上一篇: 8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
下一篇: 活动预告 | AMD/沐曦/字节/北京大学/上海创智齐聚北京,从底层编译到场景应用的多视角探索
HyperAI超神经
博客等级 码龄7年 8872粉丝 1753原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值