tvm实战(代码片段)

antkillerfarm antkillerfarm     2022-12-04     188

关键词:

TVM实战

问题的由来

最近客户反馈我们的backend导入Pytorch模型会出错,而TFLite模型是OK的。

打印模型的IR后,我们发现:

这是Pytorch模型的IR片段:

  %0 = qnn.quantize(%input, 0.0186579f, 114, out_dtype="uint8", axis=1);
  %1 = nn.pad(%0, 114f, pad_width=[[0, 0], [0, 0], [1, 1], [1, 1]]);
  %2 = qnn.quantize(%features.0.0_weight, 0.00288958f, 0, out_dtype="int8", axis=0);
  %3 = qnn.conv2d(%1, %2, 114, 0, 0.0186579f, 0.00288958f, strides=[2, 2], padding=[0, 0, 0, 0], channels=32, kernel_size=[3, 3], out_dtype="int32");
  %4 = qnn.quantize(%features.0.0_bias, 5.39136e-05f, 0, out_dtype="int32", axis=0);
  %5 = nn.bias_add(%3, %4);
  %6 = qnn.requantize(%5, 5.39136e-05f, 0, 0.0150183f, 0, axis=1, out_dtype="int32");
  %7 = clip(%6, a_min=0f, a_max=255f);
  %8 = cast(%7, dtype="uint8");

这是TFLite模型的IR片段:

  %0 = qnn.quantize(%input, 0.0186579f /* ty=float32 */, 114 /* ty=int32 */, out_dtype="uint8", axis=1) /* ty=Tensor[(1, 3, 224, 224), uint8] */;
  %1 = nn.pad(%0, 114f /* ty=float32 */, pad_width=[[0, 0], [0, 0], [1, 1], [1, 1]]) /* ty=Tensor[(1, 3, 226, 226), uint8] */;
  %2 = qnn.conv2d(%1, meta[relay.Constant][0] /* ty=Tensor[(32, 3, 3, 3), int8] */, 114 /* ty=int32 */, 0 /* ty=int32 */, 0.0186579f /* ty=float32 */, 0.00288958f /* ty=float32 */, strides=[2, 2], padding=[0, 0, 0, 0], channels=32, kernel_size=[3, 3], out_dtype="int32") /* ty=Tensor[(1, 32, 112, 112), int32] */;
  %3 = nn.bias_add(%2, meta[relay.Constant][1] /* ty=Tensor[(32), int32] */) /* ty=Tensor[(1, 32, 112, 112), int32] */;
  %4 = qnn.requantize(%3, 5.39136e-05f /* ty=float32 */, 0 /* ty=int32 */, 0.0150183f /* ty=float32 */, 0 /* ty=int32 */, axis=1, out_dtype="uint8") /* ty=Tensor[(1, 32, 112, 112), uint8] */;

可以看出同一个QnnConv2D两者的展开形式存在一定的差异,但是语义上却基本是一致的。

Relay IR

在继续主题之前,我们首先来看看这个展开形式的差异是如何造成的。

TFLite/Pytorch模型导入之后,有一个转换成Relay IR的过程,也就是所谓的frontend。

相关代码在:

TFLite: python/tvm/relay/frontend/tflite.py

Pytorch: python/tvm/relay/frontend/pytorch.py和python/tvm/relay/frontend/qnn_torch.py

可以看出Pytorch的量化模型的weight是浮点数,而TFLite的量化模型的weight是整数。

有frontend自然就有backend:

python/tvm/relay/op/contrib/ethosn.py

和切割计算图有关的代码主要是:

seq = tvm.transform.Sequential(
    [
        ......
        transform.MergeComposite(pattern_table()),
        transform.AnnotateTarget("ethos-n"),
        transform.MergeCompilerRegions(),
        transform.PartitionGraph(),
    ]
)
seq(mod)

这里的Pass基本看名字就知道功能了,唯一需要关注的是MergeComposite。

这是pattern_table的其中一个表项:

def qnn_conv_pattern():
    pattern = is_op("nn.pad")(wildcard(), wildcard()) | wildcard()
    pattern = is_op("qnn.conv2d")(
        pattern, is_constant(), is_constant(), is_constant(), is_constant(), is_constant()
    )
    pattern = is_op("nn.bias_add")(pattern, is_constant())
    pattern = is_op("qnn.requantize")(
        pattern, is_constant(), is_constant(), is_constant(), is_constant()
    )
    return pattern

一般来说IR会定的比较细粒度,而AI硬件更喜欢粗粒度的op。所以往往若干个IR op组合起来,才能得到一个AI硬件op。这时就需要进行模板匹配。

一般来说,数据可以分为变量和常量(is_constant()),如果既可能是变量,也可能是常量的话,就用wildcard()匹配。

MergeComposite会将这个模板打包成一个函数,变量会写为函数的参数,而常量放到全局的meta data里。

添加Pass

下面我们来看看如何添加Pass进行这样的转换。

class QnnQuantizeConstFold : public DFPatternRewrite 
 public:
  QnnQuantizeConstFold() 
    data_pat_ = IsConstant();
    pattern_ = IsOp("qnn.quantize")(data_pat_, IsConstant(), IsConstant());
  

  Expr Callback(const Expr& pre, const Expr& post,
                const Map<DFPattern, Array<Expr>>& node_map) const override 
    ......

    if (output->dtype == DataType::Int(8)) 
      return QuantizeData<int8_t>(......);
     else if (output->dtype == DataType::Int(32)) 
      return QuantizeData<int32_t>(......);
    
    return post;
  

 protected:
  DFPattern data_pat_;
;

Rewrite_是Pass最重要的函数。其中最简单的当属DFPatternRewrite

Callback的参数中,pre表示原始Exprpost表示替换后的Expr,返回值也是替换后的Expr。(方便使用链式调用?)

如果Pass什么都不做的话,直接返回post就可以了。

PS:虽然prepost在运行之初是相同的,但是一旦替换开始,两者就有差异了,所以如果是写入的内容,一定要引用post里的那份。

Python版的Pass

Pass不仅能用C++写,也可用python写。

class QnnQuantizeConstFold(tvm.relay.dataflow_pattern.DFPatternCallback):
    def __init__(self, require_type=False):
        super().__init__(require_type)
        self.pattern = is_op("qnn.quantize")(
            is_constant(), is_constant(), is_constant()
        )

    def callback(self, pre, post, node_map):
        ......
        if (dtype == "int8"):
             return tvm.relay.Constant(tvm.nd.array(data.astype(np.int8)))
        if (dtype == "int32"):
            return tvm.relay.Constant(tvm.nd.array(data.astype(np.int32)))
        return post

可以看出,写法也是大同小异,只是更便于集成,也不用写FFI接口了。

使用方法:

func = mod["main"]
func = tvm.relay.Function(func.params, func.body, None, func.type_params, func.attrs)
func = tvm.relay.dataflow_pattern.rewrite(RemoveClipAfterRequantize(), func)
func = tvm.relay.dataflow_pattern.rewrite(QnnQuantizeConstFold(), func)
mod = tvm.IRModule.from_expr(func)

这里展示了DFPatternCallback的使用方法,还有IRModuleExpr的相互转换方法。

参考:

https://tvm.apache.org/docs/reference/langref/relay_pattern.html

Pattern Matching in Relay

tvmc

tvmc是tvm提供的一个命令行接口。

export TARGET="bnns, llvm -device=arm_cpu -mtriple=aarch64-linux-gnu"
python3 -m tvm.driver.tvmc compile ./mobilenet_v1_0.25_224_quant.tflite --target "$TARGET" -o tvmc.tar --cross-compiler "$CC" --cross-compiler-options "$CC_OPTIONS"

要点如下:

  1. 注意TARGET的写法,这展示了如何将一个包含空格的字符串作为一个bash变量传递给命令行参数的做法。定义变量时的双引号和使用时的双引号都是必不可少的。

  2. 有些backend需要partation,将能执行的op分配到该设备上,因此TARGET也包含了两部分(用逗号分隔),其中第一部分用于partation。

相关代码在:

python/tvm/driver/tvmc/composite_target.py

tvm实战(代码片段)

TVM实战问题的由来最近客户反馈我们的backend导入Pytorch模型会出错,而TFLite模型是OK的。打印模型的IR后,我们发现:这是Pytorch模型的IR片段:%0=qnn.quantize(%input,0.0186579f,114,out_dtype="uint8",axis=1);%1=nn.... 查看详情

tvm概述(代码片段)

...易用性和后者的执行效率。官网:https://tvm.apache.org/代码:https://github.com/apache/tvm论 查看详情

markdown在tvm.relay中使用外部库(代码片段)

查看详情

markdown在tvm编译onnx模型并执行(代码片段)

查看详情

tvm在linux环境下的安装与编译及vscode如何配置tvm的远程连接调试环境(代码片段)

文章目录前言1.安装TVM1.1下载源码1.2创建虚拟环境及安装依赖库1.3编译TVM源码1.4验证安装是否成功2.配置vscode3.安装FFINavigator结束语前言  本篇文章介绍一下tvm在linux环境下的安装与编译,以及如何使用vscode来配置tvm的远程... 查看详情

tvm巡礼howtooptimizecpu(x86)gemm串讲(代码片段)

...容主要集中在对各个Schedule的单独详解以及引入了TIR的伪代码描述帮助读者更好的理解TVMSch 查看详情

tvm概述(代码片段)

...易用性和后者的执行效率。官网:https://tvm.apache.org/代码:https://github.com/apache/tvm论文:《TVM:End-to-EndOptimizationStackforDeepLearning》和同类项目的差异:TFLite和ONNXRuntime只能接收特定格式的模型。而TVM这些都能接收。NCN... 查看详情

tvm概述(代码片段)

...易用性和后者的执行效率。官网:https://tvm.apache.org/代码:https://github.com/apache/tvm论文:《TVM:End-to-EndOptimizationStackforDeepLearning》和同类项目的差异:TFLite和ONNXRuntime只能接收特定格式的模型。而TVM这些都能接收。NCN... 查看详情

tvm概述(代码片段)

...易用性和后者的执行效率。官网:https://tvm.apache.org/代码:https://github.com/apache/tvm论文:《TVM:End-to-EndOptimizationStackforDeepLearning》和同类项目的差异:TFLite和ONNXRuntime只能接收特定格式的模型。而TVM这些都能接收。NCN... 查看详情

markdown使用tvm编写可调模板和使用自动调优器(代码片段)

查看详情

tvm在linux环境下的安装与编译及vscode如何配置tvm的远程连接调试环境(代码片段)

文章目录前言1.安装TVM1.1下载源码1.2创建虚拟环境及安装依赖库1.3编译TVM源码1.4验证安装是否成功2.配置vscode3.安装FFINavigator结束语前言  本篇文章介绍一下tvm在linux环境下的安装与编译,以及如何使用vscode来配置tvm的远程... 查看详情

在x86的docker中构建tvm的arm环境(代码片段)

文章目录前言1.加载arm-ubuntu镜像2.安装acl库3.编译arm运行时4.编译在x86运行在arm4.1在x86的环境中构建arm的编译环境4.2测试x86-ubuntu与arm-ubuntu能否ping通4.3调用RPC4.4ACL的使用5.arm版的tvm编译和运行时环境5.1构建arm版的tvm编译和运行时环... 查看详情

int8量化和tvm实现(代码片段)

量化主要有两种方案直接训练量化模型如Deepcompression,Binary-Net,Tenary-Net,Dorefa-Net对训练好的float模型(以float32为例)直接进行量化(以int8为例),这边博客主要讲这个参考NIVIDIA量化官方文档int8量化原理将已有的float3... 查看详情

tvm学习指南(个人版)(代码片段)

文章目录0x0.前言0x1.前端0x1.1TensorIR(TIR)0x1.2了解tvm.ir基础设施0x1.3RelayIR0x1.4RelaxD0:数据流块作为第一优先级的构造D1:形状推导作为第一优先级的计算D1a:match_shapeD1b.从符号整数元组构造ShapeShape传播的方法Implicationsforpasswriti... 查看详情

tvm|一种用于深度学习的端到端自动优化编译器(代码片段)

 欢迎关注我的公众号[极智视界],获取我的更多笔记分享 大家好,我是极智视界,本文解读一下一种用于深度学习的端到端自动优化编译器TVM。 现在越来越需要将机器学习部署到各种硬件设备,当前的框架... 查看详情

tvm巡礼howtooptimizecpu(x86)gemm串讲(代码片段)

...容主要集中在对各个Schedule的单独详解以及引入了TIR的伪代码描述帮助读者更好的理解TVMSchedule。还有深入解读了Blocking技术以及AutoSchedule搜出的高性能程序,简要分析了microKernel的汇编。另外我制作了一个控制Schedule变量进行... 查看详情

社区实践为tvm新增oneflow前端(代码片段)

...魁同学的贡献。在这个初版的基础上,我做了一系列代码重构,BUG修复,文档编写,支持更多算子和模型转换之后使其达到了一个相对稳定的状态。所以这篇文章来分享一下做这个小项目的经历和技术细节,... 查看详情

在x86的docker中构建tvm的arm环境(代码片段)

...,并提供详细的示例以供验证,其中包括rpc测试代码,acl测试代码,pytorch模型在arm上的推理以及在arm上进行autotvm。  如下图所示,显示的是x86架构的cpu信息:  强烈建议使用ubuntu:20.04这个版本,ub... 查看详情