神经网络编译器TVM

news2024/12/25 14:20:31

市面上,关于神经网络的提速方案,可谓八仙过海各显神通

英伟达搞了tensorRT

facebook依托着pytorch也做了 libtorch c++ 的相关部署方案

谷歌在他原有的tensorflow的生态做了tensorflow c++以及tensorflow lite相关的方案

这些方案各有优劣,各有长短,不过他们有一个共有的特点,那就是推理代码,推理框架是通用的

不同的神经网络模型,都是加载到这个通用推理框架来做推理

没有做针对你专门的神经网络来做专门的比如cache命中,中间缓存,推理中间层融合等

当然通用的优化他们都做了.只是一些专用的优化

比如针对resNet有效但是对于,centerNet无效的之类优化就没有做.当然为了框架的通用性,也没法做.


TVM是一款开源项目,主要由华盛顿大学的SAMPL组贡献开发.其实是一个神经网络的编译框架

在这里插入图片描述
首先TVM有一个基础认识,也即是每个神经网络模型的运算其实分为两部分:

一部分是compute:也即是数学层面的东西,他就是我们平时说得 f ( w x + b ) f(wx+b) f(wx+b)这样的东西.在整个TVM神经网络编译过程中没有发生变化,也就是说精度不会损失,这也是他的优势;

另一部分是schedule: 也即是代码层面对这些数学逻辑实现的调度层面的东西,比如我的for循环如何设计,中间变量如何存储,cache命中率如何,寄存器访问如何设置.是否有两步合并做一步的更高效的操作.这些部分的实现,对于我们上面说的tensorRT,tensorflow之类他有统一的实现,不会对每个神经网络做定制.而这部分对于推理时间的影响十分的大.

输入是已经训练好的神经网络模型,比如onnx,tensorflow,pytorch,之类.随后通过TVM这个框架自定义的算子和计算规则表达方式,把整个模型表达为relay.也就是计算原语.

得到计算原语之后,tvm框架实现了一个算子仓库,算子仓库根据计算原语,重新组装compute和schedule。形成最终的推理代码。这一步也就是神经网络的编译过程。

编译过程,可以使用tvm默认的schedule方式,也就是默认编译方式,也可以使用自动重新搜索的方式,也就是autoTVM,一般而已如果模型目前的推理时间还比较长,比如10ms的时候,使用autotvm的方法往往能取得还不错的效果。

如上面网络架构所示,tvm编译完成后,会生成目标平台的代码,比如cuda,树莓派,苹果手机,安卓手机。生成的代码就是咱们需要的推理代码啦,平台为了我们方便的使用,同时又帮我们编译成的so库,或者dll库。不同平台动态库类型不一样。

目前TVM的架构是:

1)最高层级支持主流的深度学习前端框架,包括TensorFlow,MXNet,Pytorch等。

2)Relay IR支持可微分,该层级进行图融合,数据重排等图优化操作。

3)基于tensor张量化计算图,并根据后端进行硬件原语级优化,autoTVM根据优化目标探索搜索空间,找到最优解。

4)后端支持ARMCUDA/Metal/OpenCL、加速器VTA(Versatile Tensor Accelerator)

安装传送门:http://tvm.apache.org/docs/install/index.html

官方简介传送门:http://tvm.apache.org/docs/tutorials/index.html


什么是autoTVM

autotvm是tvm的一个子模块,使用机器学习的方法来自动搜索最优的目标硬件平台的推理代码。优点是接口简单,易于使用,在算子优化门槛高,算子优化工程师少同时市场需求巨大的背景下,通过这种自动搜索的方式,也能搜索出与手工设计优化代码相匹敌的推理代码。优化效率大大提升

典型优化方向和思路

自动优化目标函数

常见的推理schedule优化方式有 loop order , memory scope , threading ,tiling ,unrolling , vectorization 等.这里有介绍另外一个门派autoKernel 时,对这几种优化方式的详细介绍:

传送门:https://baijiahao.baidu.com/s?id=1686304642517576719&wfr=spider&for=pc

通过tvm的IR定义我们可以穷举出一个数量极其庞大的图优化和调度优化的策略。但是介于计算资源的有限性,我们不可能使用暴力的方式对他们进行搜索!

在这里插入图片描述

为了理解整个优化流程,我们先来看看节点的含义:

Exploration Module 是优化主流程,优化得到schedule方案和图优化方案,存入到history data ,

随后右边的Cost Model用于预测优化方案的耗时的相对关系是怎么样。

主要作用是预筛选出耗时小于history data的新候选方案。同时,CostModel的训练由最右边的ObjectFunction 指导。

剩下的Code Generator 和 HardWare Environment 不用做过多的解释。

接下来我们挨个模块进行介绍。

首先CostModel: 我们看看论文原文的一段介绍。

The features include loop structure information (e.g., memory access count and data reuse ratio) and generic annotations (e.g., vectorization, unrolling, thread binding). We use XGBoost [7], which has proven to be a strong feature-based model in past problems. Our second model is a TreeGRU[39], which recursively encodes a low-level AST into an embedding vector. We map the embedding vector to a final predicted cost using a linear layer.

总结起来就是CostModel是一个预测一批(e,s) 参数组合的推理时间的模型。

CostModel的输入是被我们搜索原始推理模型IR对应的两大类信息:结构信息和通用注释信息(不知道这样翻译是否合适)

结构信息包括:内存访问计数,内存重复利用率之类的信息

通用注释信息包括:矢量化信息,展开信息,线程信息

这两种信息通过XGBoost模型以及treeGRU模型,被编码为一个embedding矢量,随后这个矢量再连接一个全链接层,最后由全连接层回归预测我们的(e,s)参数组合最后生成的推理代码的推理时间。

当我们得到一个CostModel 之后,我们可以使用这个CostModel来预测推理时间,而不是跑一遍真实的推理,这样极大的节省了时间,

可以这样想象一下,通过tvm这个框架,我们有若干个这样的(e,s) 参数组合,如果我们挨个生成代码来搜索最优的候选参数组合,

那么显然这种方法是不可行的。现在有了这个CostModel就可以快速的预测参数最终的推理时间。

极大的扩大了我们有限计算资源下可以搜索的参数范围。

有了这个CostModel,我们还需要想办法让模型预测更加准确,CostModel的训练使用了一个相对目标函数的训练策略。

在实际使用情况也是如此,我们只需要关注耗时的相对关系,而不是绝对关系。

换一句话说就是从候选的一堆(e,s) 参数中快速找出排前面的组合,就达到了我们的目的,而不需要关注他们的绝对运行时间。

这样对于模型也更容易训练。


使用模拟退火,通过CostModel 预测参数的推理时间,并选择出比较优秀的一些候选参数。

从上一步选出来的参数组合中,向着多样性的方向进一步筛选过滤参数,多样性用以下的公式来约束:

  1. 从这些参数中随机选择一堆组合出来。

  2. 使用这一堆随机的参数,编译推理代码,运行真正的推理过程。根据以上流程的保证,这一步大概率能找到比上一轮跑的更快的参数。

  3. 跑过真正推理的这些参数,就相当于给CostModel 打了标签。 目标平台上的推理时间就是CostModel的标签。

  4. 运行完了这一步之后,整个流程就转起来了,CostModel不断地训练以及筛选出更多的候选参数,候选参数又能大概率的比上一波参数跑的更快。一轮一轮的迭代之后,我们就找到了最优的目标平台推理参数,用这个参数编译出目标平台推理代码。

截止这里理论层面的东西,就了解的差不多了,下面正式开始代码,下面是论文传送门:

论文:

https://arxiv.org/abs/1805.08166

https://arxiv.org/abs/1802.04799

使用TVM导入神经网络模型:

模型支持pytorch , tensorflow , onnx, caffe 等。我平时pytorch用的多,这里给一种pytorch的导入方式

def relay_import_from_torch(model, direct_to_mod_param=False):
    # 模型输入模型是 NCHW次序,另外我在综述中看到tvm目前支持动态shape
    input_shape = [1, 3, 544, 960]
    input_data = torch.randn(input_shape)
    # 使用随机数据,运行一次模型,记录张量运算
    scripted_model = torch.jit.trace(model, input_data).eval()

    input_name = "input0"
    shape_list = [(input_name, input_shape)]
    # 导入模型和权重
    mod, params = relay.frontend.from_pytorch(scripted_model, shape_list)
    if direct_to_mod_param:
        return mod, params

    # target = tvm.target.Target("llvm", host="llvm")
    # dev = tvm.cpu(0)
    # 设定目标平台和设备号,可以是其他的平台,比如ARM GPU ,苹果手机GPU等
    target = tvm.target.cuda()
    dev = tvm.device(str(target), 0)
    with tvm.transform.PassContext(opt_level=3):
    # 编译模型至目标平台,保存在lib变量中,后面可以被导出。
        lib = relay.build(mod, target=target, params=params)
    # 使用编译好的lib初始化 graph_executor ,后面用它来推理
    tvm_model = graph_executor.GraphModule(lib["default"](dev))

    return tvm_model, dev

初始化了推理需要的graph_executor,我们就来使用它进行推理吧


这里介绍另外一种,导出为so文件,然后加载so文件进行推理的方式。

使用TVM导出目标平台推理代码:

lib.export_library("centerFace_relay.so")

当然这里还没有进行schedule参数搜索,虽然相对于原始的pytorch接口也能有一定优化,但是还没有发挥最大功力。


TVM的python推理接口实践:
来,上代码。 so文件是刚才导出的推理库,也可以是后面搜索得到的推理库,等下后文介绍。

frame = cv2.imread("./ims/6.jpg")

target = tvm.target.cuda()
dev = tvm.device(str(target), 0)

lib = tvm.runtime.load_module("centerFace_relay.so")
tvm_centerPoseModel = runtime.GraphModule(lib["default"](dev))

input_tensor, img_h_new, img_w_new, scale_w, scale_h, raw_scale = centerFacePreprocess(frame)
tvm_centerPoseModel.set_input("input0", tvm.nd.array(input_tensor.astype("float32")))

for i in range(100):
    # 推理速率演示,推理多次后时间会稳定下来
    t0 = time.time()
    tvm_centerPoseModel.run()
    print("tvm inference cost: {}".format(time.time() - t0))

heatmap, scale, offset, lms = torch.tensor(tvm_centerPoseModel.get_output(0).asnumpy()), \
                              torch.tensor(tvm_centerPoseModel.get_output(1).asnumpy()), \
                              torch.tensor(tvm_centerPoseModel.get_output(2).asnumpy()), \
                              torch.tensor(tvm_centerPoseModel.get_output(3).asnumpy()),

dets, lms = centerFacePostProcess(heatmap, scale, offset, lms, img_h_new, img_w_new, scale_w, scale_h, raw_scale)

centerFaceWriteOut(dets, lms, frame)

这里就打通了一个完整的流程,使用tvm导入模型 —> 编译并导出so库 —> 加载so库 —> 推理

上面的编译并导出so库,在windows平台的话就是导出dll 库。

编译的过程使用tvm默认的schedule参数,也有一定的优化效果,测试下来,

之前使用了一个centerface的pytorch模型推理50W像素的图片大约需要12ms [ 1080ti ],默认编译后推理时间大约是 6ms 。

对比上面,除了使用默认的schedule参数进行推理,还可以搜索更优的schedule参数。

测试下来,相同的情况,centerface推理时间3.5ms。 又有了大约一倍的提升

对应的总体流程变成了:

使用tvm导入模型 —> 搜索最优scheduel参数 — > 编译并导出so库 —> 加载so库 —> 推理


使用autoTVM搜索最优推理代码:

python 搜索代码.

def case_autotvm_relay_centerFace():
    # InitCenterFacePy封装了pytorch的	       加载代码
    model = InitCenterFacePy()
    # tvm搜索完成后将结果保存在.log中
    log_file = "centerFace.log"
    dtype = "float32"
    # 初始化优化器,及优化选项
    tuning_option = {
        "log_filename": log_file,
        "tuner": "xgb",
        # "n_trial": 1,
        "n_trial": 2000,
        "early_stopping": 600,
        "measure_option": autotvm.measure_option(
            builder=autotvm.LocalBuilder(timeout=10),
            runner=autotvm.LocalRunner(number=20, repeat=3, timeout=4, min_repeat_ms=150),
        ),
    }
    print("Extract tasks centerFace...")
    mod, params, = relay_import_from_torch(model.module.cpu(), direct_to_mod_param=True)
    input_shape = [1, 3, 544, 960]
    target = tvm.target.cuda()
    tasks = autotvm.task.extract_from_program(
        mod["main"], target=target, params=params, ops=(relay.op.get("nn.conv2d"),)
    )

    # run tuning tasks
    print("Tuning...")
    tune_tasks(tasks, **tuning_option)

    # compile kernels with history best records
    # 模型搜索完成后,进行耗时统计。
    profile_autvm_centerFace(mod, target, params, input_shape, dtype, log_file)

TVM验证推理时间:

def profile_autvm_centerFace(mod, target, params, input_shape, dtype, log_file):
    with autotvm.apply_history_best(log_file):
        print("Compile...")
        with tvm.transform.PassContext(opt_level=3):
            lib = relay.build_module.build(mod, target=target, params=params)

        # load parameters
        dev = tvm.device(str(target), 0)
        module = runtime.GraphModule(lib["default"](dev))
        data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype))
        module.set_input("input0", data_tvm)

        # evaluate
        print("Evaluate inference time cost...")
        ftimer = module.module.time_evaluator("run", dev, number=1, repeat=100)
        prof_res = np.array(ftimer().results) * 1000  # convert to millisecond
        print(
            "Mean inference time (std dev): %.2f ms (%.2f ms)"
            % (np.mean(prof_res), np.std(prof_res))
        )
        lib.export_library("centerFace_relay.so")

TVM的c++推理接口实践
上面我们得到了一个目标平台编译好的动态库。

神经网络的部署不仅仅是推理,还有其他的代码,往往都是一些效率要求很高的场景,我们一般都使用c++作为目标平台的编码语言。

so库得到之后,我们如何使用他来推理呢

初始化部分

 DLDevice dev{kDLGPU, 0};

    // for windows , the suffix should be dll
    mod_factory = tvm::runtime::Module::LoadFromFile(lib_path, "so");
    // 通过动态库获取模型实例 gmod
    gmod = mod_factory.GetFunction("default")(dev);
    // 获取函数指针: 设置推理输入
    set_input = gmod.GetFunction("set_input");
    get_output = gmod.GetFunction("get_output");
    run = gmod.GetFunction("run");

    // Use the C++ API
    // 输入输出的内存空间 gpu设备上
    x = tvm::runtime::NDArray::Empty({1, 3, 544, 960}, DLDataType{kDLFloat, 32, 1}, dev);
    heatmap_gpu = tvm::runtime::NDArray::Empty({1, 1, 136, 240}, DLDataType{kDLFloat, 32, 1}, dev);
    scale_gpu = tvm::runtime::NDArray::Empty({1, 2, 136, 240}, DLDataType{kDLFloat, 32, 1}, dev);
    offset_gpu = tvm::runtime::NDArray::Empty({1, 2, 136, 240}, DLDataType{kDLFloat, 32, 1}, dev);
    lms_gpu = tvm::runtime::NDArray::Empty({1, 10, 136, 240}, DLDataType{kDLFloat, 32, 1}, dev);

推理部分

值得注意的是: cv::dnn::blobFromImage真是一个好用的函数,制动帮你构造好 NCHW排列的输入内存块,而且opencv还内置了openmp 加速,在树莓派,各种手机上的时候这个函数也很好用。

int h = frame.rows;
int w = frame.cols;
float img_h_new = int(ceil(h / 32) * 32);
float img_w_new = int(ceil(w / 32) * 32);
float scale_h = img_h_new / float(h);
float scale_w = img_w_new / float(w);
cv::Mat input_tensor = cv::dnn::blobFromImage(frame, 1.0, cv::Size(img_w_new, img_h_new),
                                              cv::Scalar(0, 0, 0),
                                              true,
                                              false, CV_32F);
x.CopyFromBytes(input_tensor.data, 1 * 3 * 544 * 960 * sizeof(float));

set_input("input0", x);
timeval t0, t1;
gettimeofday(&t0, NULL);
run();
gettimeofday(&t1, NULL);
printf("inference cost: %f \n", t1.tv_sec - t0.tv_sec + (t1.tv_usec - t0.tv_usec) / 1000000.);
get_output(0, heatmap_gpu);
get_output(1, scale_gpu);
get_output(2, offset_gpu);
get_output(3, lms_gpu);
tvm::runtime::NDArray heatmap_cpu = heatmap_gpu.CopyTo(DLDevice{kDLCPU, 0});
tvm::runtime::NDArray scale_cpu = scale_gpu.CopyTo(DLDevice{kDLCPU, 0});
tvm::runtime::NDArray offset_cpu = offset_gpu.CopyTo(DLDevice{kDLCPU, 0});
tvm::runtime::NDArray lms_cpu = lms_gpu.CopyTo(DLDevice{kDLCPU, 0});

参考文献

  • https://zhuanlan.zhihu.com/p/366913595

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/11882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis数据结构之——intset

写在前面 以下内容是基于Redis 6.2.6 版本整理总结 一、整数集合(intset) 当一个集合只包含整数值元素,并且元素的个数不多时,Redis会使用整数集合作为集合键的底层实现。 1.1 整数集合的实现 整数集合可用保存的数据类型有&a…

MetersPhere提取信息头token并设置为全局变量

MetersPhere提取信息头token并设置为全局变量 我们需要登录接口的token参数,如下 在下面这一行了 Set-Cookie: tokenIdA552326CDC8F4A19B454DADF8938980B; path/参考了其他的文章,再加上自己的理解,现在提供两种方法,先把先驱的脚本拿出来…

Dragonframe是一个全功能的动画制作工具,专为满足电影,广播电视和电影的要求设计。

将 Dragonframe 置于您下一部定格动画电影的核心位置。 动画片 让您的动画栩栩如生。用于精确移动的专业屏幕工具。步进、播放、捕捉、重复。 摄影 使用相机控制、测试镜头和高级图像查看工具来构图和点亮完美的镜头。 声音的 导入和编辑多个音轨。对多个角色进行对话曲目阅读。…

我的力扣刷题顺序(参考代码回忆录)

数组 数组过于简单,但你该了解这些!数组:二分查找数组:移除元素数组:序数组的平方数组:长度最小的子数组数组:螺旋矩阵II数组:总结篇 链表 关于链表,你该了解这些&…

当光伏巡检走向全自动化

作者 | 曾响铃 文 | 响铃说 无数的太阳能面板组成光伏发电的海洋,在烈日下矗立,为了保证它们正常运行,电站必须安排人力巡查,一块块面板全面检查,周而复始。 在光伏发电高速发展的这些年,这一幕已经成为…

餐饮美食网页设计(HTML+CSS+JavaScript)

🎀 精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

hiveSql 跨N天最大连续统计

hiveSql 跨N天最大连续统计说明需求分析实现最后说明 看到标题可能没太能理解,什么叫跨N天连续?这里解释下: 跨N天连续定义为:登录相隔天数小于N 用跨2天举例(即隔一天也算连续登录) 例如 : 20…

书桌台灯怎么选?分享儿童卧室灯品牌

书桌少不了的就是台灯,从小到大,不管是学习还是工作,都离不开一张书桌,学生上学学习,书桌就是必不可少的一部分,而选择怎么样的台灯好呢? 最重要的是安全的的光线品质 在照明领域,光…

Matlab 实现 信号的基本运算

实验名称 利用 Matlab 软件的信号处理工具箱(Signal Processing Toolbox)中的专用函数来实现信号的基本运算。 包括 - * 时移、翻转、展缩 运算 实验环境 MATLAB - R2021b 实验目的 了解仿真基本的信号。 sin() 产生正弦信号 ysin(x) x 是自变量 可看做时间向量 plot() …

Quick MTF 照相机镜头图像质量测试程序-UPDATE

Quick MTF,图像质量测试应用程序 Quick MTF 可让您测试数字图像的质量。它具有精确的计算引擎和直观的用户界面,无需特殊培训即可轻松使用。只需使用 Quick MTF 打开测试图像并选择目标区域。 目的 Quick MTF 检查成像设备拍摄的图像质量,让…

【MySQL | 运维篇】07、MySQL 分库分表之 MyCat 管理与监控

目录 一、MyCat原理 二、MyCat管理 三、MyCat-eye 3.1 介绍 3.2 安装 1). zookeeper安装 2). Mycat-web安装 3.3 访问 3.4 配置 1). 开启MyCat的实时统计功能(server.xml) 2). 在Mycat监控界面配置服务地址 3.5 测试 A. 性能监控 B. 物理节点 C. SQL统计 …

【录用案例】CCF-B类,3区智能传感器类SCIEI,仅2个月录用

3区智能传感器类SCI&EI 【出版社】ACM 出版社 【期刊简介】IF:2.0-3.0,JCR3区,中科院4区 【检索情况】SCI&EI 双检,正刊,CCF-B类 【征稿领域】 ①智能城市的绿色通信和传感器网络与机器智能(已截稿&#…

【刷题】二叉树遍历思路解析

二叉树遍历 (牛客网) 题目要求: 编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“…

预览ppt时中文乱码

现象:预览ppt时中文乱码 原因:该后端服务部署的主机没有指定中文字体 解决:用root角色在主机目录 /usr/share/fonts 上传Chinese字体文件 1、上传解压后,去应用上测试,发现页面全空白的, 且后台服务日志…

擎创技术流 | ckman教程(3)CKman源码分析部署集群的主要步骤

叮~您有一个新的技术分享已送达,请注意查收~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 哈喽,各位知乎友友们~ckman使用教程分享已经到第3期啦,不知道大家都掌握了没有呢?没有的话也没关系,点击下方链接,一键回…

centos7 + mysql 8 安装confluence7.19.3

一、安装包下载 1、访问下载连接,进行下载最新的安装包 Confluence Server 下载存档 | Atlassian mkdir -p /data/soft cd /data/soft wget https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-7.19.3.tar.gz 2、数据库准备 …

单元测试(jest):理解、安装、使用

一、理解单元测试的重要性 bug发现在开发阶段,成本很低, 如果发现在生产环境,成本很高, 如果是关键时刻,决定人生命运,决定企业发展。 从技术的角度讲,有效的提高代码的健壮性,有效…

GO语言集成开发工具环境JetBrains GoLand 2022

JetBrains GoLand 2022是一款专业的GO语言编程软件。JetBrains GoLand支持编码辅助功能,IDE会分析你的代码,然后在符号之间寻找连接。提供代码提示,快速导航,灵活的错误分析能力以及格式化和重构功能。JetBrains GoLand 2022 强大…

docker安装es+mac安装Kibana工具+es查询语法笔记

一、docker安装es 1、下载镜像 docker pull elasticsearch:7.9.0下载完后,查看镜像 docker images​​ 2、启动镜像 docker network create esnetdocker run -d --name es -p 9200:9200 -p 9300:9300 --network esnet -e "discovery.typesingle-node&…

图解CentOS7集群时钟同步chronyd

文章目录概述图步骤1、修改时区2、安装chrony3、选1台机作为时钟服务器4、其他机器同步时间概述 对于物理机集群,需要使用统一的时间,本文使用时钟同步技术来实现 图 图解 默认的 外部网络的 时钟服务器 在国外,速度较慢 0.centos.pool.n…