深度学习模型部署-番外-TVM机器学习编译

什么是机器学习编译器/AI编译?

在这里插入图片描述

图片来自知乎大佬的文章

机器学习编译是指:将模型从训练形式转变为部署模式

  • 训练模式:使用训练框架定义的模型
  • 部署模式:部署所需要的模式,包括模型每个步骤的实现代码,管理资源的控制器,与应用程序开发环境的接口。

这个行为和传统的编译很像,所以称为机器学习编译,但是这里的编译和传统编译不完全一样,这里的输入是网络模型,输出可能是网络模型,也可能是生成的对于推理库函数的调用代码。
机器学习的三个目标:

  • 集成与最小化依赖:将必要的元素组合成一个程序,尽量减小应用大小,并且减小对外部环境依赖,使得模型可以部署到更多环境。
  • 利用硬件加速:
  • 通用优化

为什么学AI编译

在这里插入图片描述
AI发展越来越快,各种模型不断更新,像以前那种手写plugin去实现算子的时代估计很快就会过去,这就像以往人们手工提取数据特征一样,过去的特征工程,在大模型时代也逐渐落寞,而prompt变得更加重要。我们现在处在AI时代的黎明,目前AI还没有和各行各业相结合,有效推动社会生产力的发展,我们还可以容忍低效部署,等到将来,AI编译,端到端部署一定会取代人手工写算子,所以学AI编译非常有必要。

AI编译的要素

  • 数据类型/张量:输入,输出,中间变量的存储
  • 算子/张量函数:网络模型中的计算称为张量函数,张量函数不一定非要和模型中的算子定义完全一致,可以进行融合或者优化。
    请添加图片描述
    AI编译要做的事情也围绕这两个要素展开,优化张量函数,把张量函数的一种实现替换为另一种实现【张量函数不一定非要对应模型中的一个算子,多个算子也可以一起实现,总之,模型是一个计算图,在不影响结果的情况下,内部实现可以与模型定义不完全一致。】
    请添加图片描述

模型是计算的抽象,我们使用抽象 (Abstraction)来表示我们用来表示相同张量函数的方式。不同的抽象可能会指定一些细节,而忽略其他实现(Implementations)细节,对于一个模型,我们可以抽象为左边这种,将relu和线性层分隔开,也可以定义成中间这样,将两个结合到一起,这些都是抽象,与实现无关,只要能确保实现和抽象的结果一致即可,抽象是指定做什么,实现是具体怎么做,二者没有具体的界限,因为怎么做也可以理解为一种抽象,然后再向下细分怎么做,就比如循环本身是一个实现,也可以是一种抽象,底层用并行实现。

MLC 实际上是在相同或不同抽象下转换和组装张量函数的过程。我们将研究张量函数的不同抽象类型,以及它们如何协同工作以解决机器学习部署中的挑战。

TensorIR抽象

安装TVM:

python3 -m  pip install mlc-ai-nightly -f https://mlc.ai/wheels

上面提的抽象啦,实现啦,都是为了这个做铺垫,IR,中间层表示。学过编译原理的应该非常熟悉,对的,刚才讲的就是这个意思,把网络模型翻译成一种统一的程序抽象:TensorIR,然后具体实现由backend来完成,刚才提到的实现也是一种抽象也是在为下面做铺垫,因为IR也可以分为high IR,middle IR,hardware IR,依次向下,对于下一层的IR来说,上一层就是抽象,这种IR分层的好处是可以把专用优化和通用优化分离开,根据硬件平台进行最大程度实现模型优化。
关于抽象于实现的关系,以及抽象之间的转化,实战代码:

import numpy as np
import tvm
from tvm.ir.module import IRModule
from tvm.script import tir as Tdtype = "float32"
a_np = np.random.rand(128, 128).astype(dtype)
b_np = np.random.rand(128, 128).astype(dtype)
# a @ b is equivalent to np.matmul(a, b)
c_mm_relu = np.maximum(a_np @ b_np, 0)
# Numpy底层调用OpenBLAS等底层库以及自己的一些C语言实现来执行计算# 等效实现
def lnumpy_m_relu(A:np.ndarray,B:np.ndarray,C:np.ndarray):Y = np.empty(A.shape,dtype='float32')for i in range(128):for j in range(128):for k in range(128):if k == 0:Y[i,j] = 0Y[i,j] += A[i,k] * B[k,j]C[i,j] = max(Y[i,j],0)c_np = np.empty((128, 128), dtype=dtype)
lnumpy_m_relu(a_np, b_np, c_np)
np.testing.assert_allclose(c_np, c_mm_relu, rtol=1e-5)# 使用TVM的TensorIR来实现
# TVM的TensorIR是一种用于表示计算的中间表示,它是一种低级的表示,用于表示计算的数据流和计算的依赖关系
# 它是由TVMScript语言来实现的,这是一种嵌入在Python AST中的DSL,用于表示TVM的中间表示
@tvm.script.ir_module # 修饰器,用于声明MyModule是一个TVM的IRModule,IRModule是在机器学习编译中保存张量函数集合的容器。
class MyModule:@T.prim_func # 修饰器,用于声明一个TVM的primitive functiondef mm_relu(A:T.Buffer((128, 128), dtype="float32"),B:T.Buffer((128, 128), dtype="float32"),C:T.Buffer((128, 128), dtype="float32")):T.func_attr({"global_symbol": "mm_relu","tir.noalias": True})# T.func_attr是一个语法糖,用来声明函数的属性,global_symbol对应函数名,tir.noalias表示所有的缓冲存储器都是不重叠的。Y = T.alloc_buffer((128, 128), "float32")for i,j,k in T.grid(128,128,128): # T.grid()返回的是一个生成器,可以用for循环来遍历,是TensorIR的一个语法糖with T.block("Y"): # T.block()是一个语法糖,用来定义一个block,这个block可以包含多个axis以及围绕这些axis的计算vi = T.axis.spatial(128, i) # 定义了一个空间轴,表示i的范围是[0,128),声明了轴的属性spatial,表示这是一个空间轴vj = T.axis.spatial(128, j) vk = T.axis.reduce(128, k) # 还有一种属性是reduce,表示这是一个规约轴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.block("C"):vi = T.axis.spatial(128, i)vj = T.axis.spatial(128, j)C[vi, vj] = T.max(Y[vi, vj], T.float32(0))# 块设计有助于我们进行机器学习编译分析,我们总是在空间轴上做并行化,但是在规约轴上做并行化需要考虑数据依赖关系# 每个块轴直接映射到外部循环迭代器的情况下,我们可以使用 T.axis.remap 在一行中声明所有块轴。
@tvm.script.ir_module
class MyModuleWithAxisRemapSugar:@T.prim_funcdef mm_relu(A:T.Buffer((128, 128), dtype="float32"),B:T.Buffer((128, 128), dtype="float32"),C:T.Buffer((128, 128), dtype="float32")):T.func_attr({"global_symbol": "mm_relu","tir.noalias": True})Y = T.alloc_buffer((128, 128), "float32")for i,j,k in T.grid(128,128,128):with T.block("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.block("C"):vi, vj = T.axis.remap("SS", [i, j])C[vi, vj] = T.max(Y[vi, vj], T.float32(0))# IRModule可以包含多个张量函数
@tvm.script.ir_module
class MyModuleWithTwoFunctions:@T.prim_funcdef mm(A:T.Buffer((128, 128), dtype="float32"),B:T.Buffer((128, 128), dtype="float32"),Y:T.Buffer((128, 128), dtype="float32")):T.func_attr({"global_symbol": "mm","tir.noalias": True})for i,j,k in T.grid(128,128,128):with T.block("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]@T.prim_funcdef relu(A: T.Buffer((128, 128), "float32"),B: T.Buffer((128, 128), "float32")):T.func_attr({"global_symbol": "relu", "tir.noalias": True})for i, j in T.grid(128, 128):with T.block("B"):vi, vj = T.axis.remap("SS", [i, j])B[vi, vj] = T.max(A[vi, vj], T.float32(0))# 张量函数的变换
# 张量函数的变换是指对张量函数的变换,包括对块轴的变换,对块的变换,对循环迭代器的变换等def lnumpy_mm_relu_v2(A:np.ndarray,B:np.ndarray,C:np.ndarray):Y = np.empty(A.shape,dtype='float32')for i in range(128):for j0 in range(32):for k in range(128):for j1 in range(4):j = j0*4+j1if k == 0:Y[i,j] = 0Y[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)c_np_v2 = np.empty((128, 128), dtype=dtype)
lnumpy_mm_relu_v2(a_np, b_np, c_np_v2)
np.testing.assert_allclose(c_np_v2, c_mm_relu, rtol=1e-5)# 进行张量函数变换
sch = tvm.tir.Schedule(MyModule)
# 创建一个辅助的Schedule对象,用于对张量函数进行变换block_Y = sch.get_block("Y", func_name="mm_relu")
i,j,k = sch.get_loops(block_Y)
# 获得块和循环的引用j0,j1 = sch.split(j, factors=[None,4])
# 将j轴分裂为j0和j1两个轴,j0的范围是[0,32),j1的范围是[0,4)sch.reorder(i,j0,k,j1)
# 重新排列块轴的顺序block_C = sch.get_block("C", func_name="mm_relu")
sch.reverse_compute_at(block_C, j0)
# 将C块的计算位置移动到j0轴sch.decompose_reduction(block_Y, k)
# 将Y的元素初始化和计算分解开#最后变换后的实现等价于lnumpy_mm_relu_v3
def lnumpy_mm_relu_v3(A: np.ndarray, B: np.ndarray, C: np.ndarray):Y = np.empty((128, 128), dtype="float32")for i in range(128):for j0 in range(32):# Y_initfor j1 in range(4):j = j0 * 4 + j1Y[i, j] = 0# Y_updatefor k in range(128):for j1 in range(4):j = j0 * 4 + j1Y[i, j] = Y[i, j] + A[i, k] * B[k, j]# Cfor j1 in range(4):j = j0 * 4 + j1C[i, j] = max(Y[i, j], 0)c_np = np.empty((128, 128), dtype=dtype)
lnumpy_mm_relu_v3(a_np, b_np, c_np)
np.testing.assert_allclose(c_mm_relu, c_np, rtol=1e-5)

本文是 机器编译课程的学习笔记。

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

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

相关文章

什么是代理IP?TikTok运营需要知道的IP知识

对于运营TikTok的从业者来说,IP的重要性自然不言而喻。 在其他条件都正常的情况下,拥有一个稳定,纯净的IP,你的视频起始播放量很可能比别人高出不少,而劣质的IP轻则会限流,重则会封号。那么,如何…

插入排序+希尔排序

目录 插入排序&#xff1a; 希尔排序&#xff1a; 插入排序&#xff1a; 注意这里不要将插入排序和冒泡排序弄混&#xff1a; 插入排序是将数据不断放入前一个有序数列&#xff1a; // 插入排序 void InsertSort(int* a, int n) {for (int j 1; j < n; j){for (int i j;…

Java类的多态作用及解析

多态是面向对象编程中一个重要的特性。简单来说&#xff0c;多态就是指同一个方法在不同的对象上有不同的实现。通过多态&#xff0c;我们可以在运行时根据对象的实际类型来动态地调用相应的方法&#xff0c;从而提高代码的灵活性和可扩展性。 以下是 Java 类中多态的一些作用…

如何用HBuider x网页制作蜡笔小新

目录 下载软件 ​编辑 一.制作蜡笔小新个人介绍界面 二.制作蜡笔小新我的偶像界面 三.制作蜡笔小新我的家乡界面 四.制作蜡笔小新会员注册界面 下载软件 一、HBuilder IDE的下载 HBuilder下载官网地址&#xff1a;http://www.pc6.com/mac/140609.htmlHBuilderX官方电脑版…

【机器学习-07】逻辑回归(Logistic Regression)的介绍和python实现

Logistic Regression 虽然被称为回归&#xff0c;但其实际上是分类模型&#xff0c;并常用于二分类。主要用来表示某件事情发生的可能性&#xff0c;因此因变量的范围在 0 和 1 之间。Logistic Regression 因其简单、可并行化、可解释强深受工业界喜爱。例如&#xff0c;探讨引…

前端静态开发案例-基于H5C3开发的仿照视频网站的前端静态页面-2 样式表部分和效果展示

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载 charset "utf-8"; /* 程序员田宝宝原创版权所有&#xff0c;仿冒必究&#xff0c;该界面是仿照某视频网站官网开发的静态页面 */ …

基于Jenkins + Argo 实现多集群的持续交付

作者&#xff1a;周靖峰&#xff0c;青云科技容器顾问&#xff0c;云原生爱好者&#xff0c;目前专注于 DevOps&#xff0c;云原生领域技术涉及 Kubernetes、KubeSphere、Argo。 前文概述 前面我们已经掌握了如何通过 Jenkins Argo CD 的方式实现单集群的持续交付&#xff0c…

java 继承(下)

前面我们已经说明了什么是继承&#xff1f;继承的好处弊端等&#xff0c;不清楚的可参照链接 java 继承&#xff08;上&#xff09;-CSDN博客 本篇文章主要理解 继承中变量&#xff0c;构造方法&#xff0c;成员方法的访问特点。 1、继承中变量的访问特点 1.1 代码实现 不看…

若依添加页面

背景&#xff1a;我想增加的是一个收支管理的页面 views中直接添加income文件夹&#xff0c;里面放着index.vue 网页的菜单中添加这个页面的菜单

基于springboot的留守儿童爱心网站

技术&#xff1a;springbootmysqlvue 一、系统背景 现代社会&#xff0c;由于经济不断发展&#xff0c;旧物捐赠的数量也在不断的增加&#xff0c;人们对留守儿童爱心信息的需求也越来越高。 以往的留守儿童爱心的管理&#xff0c;一般都是纸质文件来管理留守儿童爱心信息&am…

分布式异步任务框架celery

Celery介绍 github地址&#xff1a;GitHub - celery/celery: Distributed Task Queue (development branch) 文档地址&#xff1a;Celery - Distributed Task Queue — Celery 5.3.6 documentation 1.1 Celery是什么 celery时一个灵活且可靠的处理大量消息的分布式系统&…

Java:设计模式

文章目录 参考简介工厂模式简单工厂模式工厂方法模式抽象工厂模式总结 单例模式预加载懒加载线程安全问题 策略模式 参考 知乎 简介 总体来说设计模式分为三类共23种。 创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模…

基于浏览器localStorage作为数据库完成todolsit项目

一、文章内容 TodoList结构搭建HTML代码 TodoList样式编写Css代码 TodoList行为表现JavaScript代码 二、项目展示 项目介绍 Todolist是一个基于B/S模式开发的待办事项软件&#xff0c;主要功能是离线记录用户的待办事项和已经完成的事情&#xff0c;基于htmlcssjs实现&am…

【C++】---string的模拟

【C】---string的模拟 一、string类实现1.string类的构造函数2.swap&#xff08;&#xff09;函数3.拷贝构造函数4.赋值运算符重载5.析构6.迭代器7.operator[ ]8.size9.c_str&#xff08;&#xff09;10.reserve&#xff08;&#xff09;11.resize&#xff08;&#xff09;12.p…

flutter 局部view更新,dialog更新进度,dialog更新

局部更新有好几种方法&#xff0c;本次使用的是 StatefulBuilder 定义 customState去更新对话框内容 import package:flutter/cupertino.dart; import package:flutter/material.dart;class ProgressDialog {final BuildContext context;BuildContext? dialogContext;double _…

【DL经典回顾】激活函数大汇总(四十一)(SinReLU附代码和详细公式)

激活函数大汇总(四十一)(SinReLU附代码和详细公式) 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里,激活函数扮演着不可或缺的角色,它们决定着神经元的输出,并且影响着网络的学习能…

8节点空间壳单元Matlab有限元编程 | 曲壳单元 | 模态分析 | 3D壳单元 | 板壳理论| 【源代码+理论文本】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

Mysql的行级锁

MySQL 中锁定粒度最小的一种锁&#xff0c;是 针对索引字段加的锁 &#xff0c;只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小&#xff0c;并发度高&#xff0c;但加锁的开销也最大&#xff0c;加锁慢&#xff0c;会出现死锁。行级锁和存…

数据结构面试常见问题之Insert or Merge

&#x1f600;前言 本文将讨论如何区分插入排序和归并排序两种排序算法。我们将通过判断序列的有序性来确定使用哪种算法进行排序。具体而言&#xff0c;我们将介绍判断插入排序和归并排序的方法&#xff0c;并讨论最小和最大的能区分两种算法的序列长度。 &#x1f3e0;个人主…

Postman接口做关联测试的方法步骤

应用场景 假设下一个接口登录需要上一个接口的返回值&#xff0c;例如请求需要先登录获取到token&#xff0c;下一个请求要携带对应的token才能进行请求 方法&#xff1a;通过设置全局变量/环境变量 方法一&#xff1a;设置全局变量 1.先请求登录接口&#xff0c;请求成功之后…