1.TinyML-TVM是源码如何驯服Tiny的(上)
TinyML-TVM是如何驯服Tiny的(上)
低成本、人工智能驱动的源码消费类设备的激增,激发了研究者对“裸智能”(低功耗、源码通常无操作系统)设备的源码兴趣。传统上,源码专家能在这些设备上运行某些模型,源码话题圈子源码但为不同设备优化模型是源码个挑战,往往需要针对设备的源码手动优化。尤其是源码针对没有Linux支持的平台,没有可扩展的源码模型部署解决方案。因此,源码开发者通常需要创建一次性的源码定制软件堆栈,以管理资源和调度模型执行。源码
尽管机器学习软件的源码优化并不是裸机领域特有的难题,它与GPU和FPGA等硬件后端的源码问道炼丹源码开发人员共同面对。TVM展现出了适应新硬件目标的能力,但在微控制器独特配置的挑战下,它之前还未能提供足够的支持。为解决这一问题,TVM扩展了微控制器后端,即µTVM(发音为“MicroTVM”),以在裸机设备上执行张量程序,并通过TVM内置的张量程序优化器AutoTVM自动优化这些程序。上图展示了µTVM+AutoTVM基础设施的概览。
µTVM+AutoTVM的实际应用
在讨论TVM/MicroTVM及其工作原理之前,我们先看一个实际应用示例。假设我们使用STMFZG板,它配备了一个强大的ARM Cortex-M7处理器,非常适合边缘人工智能应用。插入 搜索 源码通过USB-JTAG端口将板连接至桌面,使用OpenOCD打开与设备的JTAG连接,从而通过µTVM使用设备无关的TCP套接字控制M7处理器。在桌面上运行TVM代码,执行CIFAR-分类器,如完整脚本所示:
在这个设置中,性能表现可能不佳,但AutoTVM提供了一线生机。通过为设备编写调度模板并进行一轮自动调整,可以显著提升性能。具体来说,只需替换原始构建行:
然后替换为:
这样,结果将显著提升,性能大约提升2倍,jq iframe 源码与CMSIS-NN版本5.7.0(commit ab7c9a)相比,后者是一个手工优化的ML内核库。
µTVM的性能表现与CMSIS-NN模型相比较具竞争力,但工作才刚刚开始,利用TVM的优化特性还有更多空间。通过加速密集/全连接运算(如密集/全连接操作)并利用TVM的模型特定量化和运算符融合功能,可以进一步优化性能。µTVM与TVM能够协同工作,发挥最佳性能。
µTVM的设计理念
µTVM旨在满足设备最低公分母的要求,只需提供设备的C交叉编译器工具链、读/写设备内存的方法、设备内存布局和体系结构特征的规范以及为设备准备函数执行的代码段。大多数裸机设备都支持C和JTAG,房价牌 源码因此(1)和(2)通常是免费的。此外,(3)和(4)要求通常较小。例如,STMF系列板的(3)和(4)示例如下:
µTVM基础设施和设备runtime的构建是为了满足这些需求,正努力通过支持常见的开源runtime平台(如mBED OS)来简化编译和链接过程。
µTVM的设备会话
考虑到微控制器的网络特性,引入了微会话的概念,它稍微偏离了标准的TVM代码。µTVM中的每一项功能都依赖于与目标设备的开放会话。在第一个代码片段中,一行代码偏离了规范,即这一行:
通过这个with块内的每一行,都可以调用µTVM中的函数,上下文是device_config指定的设备。这条线背后做了很多工作,让其拆分如下:
首先,它初始化与设备的连接,使用指定的任何通信方法(通常是OpenOCD)。然后使用指定的交叉编译器交叉编译µTVM设备runtime。最后,主机为编译后的二进制文件分配空间,并使用打开的连接将二进制文件加载到设备上。
由于runtime现在位于设备上,自然需要一些函数来运行它。
模块加载
TVM的核心抽象之一是模块。模块为特定设备/ runtime目标存储一组相关函数。考虑到微控制器通常没有操作系统,µTVM需要额外的工作来维护这种高级抽象。跟踪创建和加载µTVM兼容模块的过程:
假设有一个微型会议打开设备,并实现二维卷积的TVM调度。如果想把它加载到微控制器上,需要将C代码发出。为了实现这一点,只需要设定目标tvm.build或relay.build,例如:
然后,通过µTVM基础设施中的核心功能运行它:create_micro_mod:
这样,交叉编译模块中的C源代码,为生成的二进制文件分配空间,然后将二进制文件的每个部分发送到设备上分配的插槽中。一旦模块二进制文件在设备内存中处于合适的位置,二进制文件中的函数指针将被修补,使模块能够在设备runtime访问帮助函数(例如,分配草稿行)。
加载内核后,可以获取卷积函数的远程句柄,如下所示:
张量加载
如果要调用运算符,首先需要一些张量作为参数:
然后,根据其数据类型(例如int8、float等)和形状,计算每个张量的字节大小,主机在设备堆上分配内存区域。接着将张量的数据加载到分配的区域中。
函数调用
运算符执行可能是系统中最复杂的部分。为了简化表示,我们首先讨论严格执行(运算符一被调用就立即执行),然后是延迟执行(只有在需要运算符的结果时才执行运算符),这是系统的实际工作方式。
严格执行
调用函数时,输入和输出张量都作为参数传递,这就是目标传递样式:
考虑到这些张量已经在设备上分配,只需要向设备发送元数据(设备地址、形状和数据类型),设备就能知道使用哪个驻留张量。下面显示了一个名为“runtime”的函数调用。在构造这个表示之前,需要将元数据序列化到设备上专门为此目的而存在的arguments部分中。
µTVM会有一个全局UTVMTask实例,从主机端写入该实例。一旦写入任务,runtime就拥有了执行函数所需的一切,可以在runtime的入口点开始执行。runtime执行一些轻量级初始化,运行运算符,然后将控制权返回给主机。