TinyML-TVM是如何驯服Tiny的(上)

低成本、人工智能驱动的消费类设备的激增,导致了ML研究人员和从业者对“裸智能”(低功耗,通常没有操作系统)设备的广泛兴趣。虽然专家已经可以在一些裸机设备上运行某些模型,但是为不同设备集优化模型是一个挑战,通常需要手动优化特定于设备的库。对于那些没有Linux支持的平台,没有可伸缩的模型部署解决方案。正因为如此,为了瞄准新设备,开发人员必须实现一次性定制软件堆栈,以管理系统资源和调度模型执行。

机器学习软件的手动优化并不是裸机领域所独有的。事实上,这一直是与其他硬件后端(例如,gpu和fpga)一起工作的开发人员的共同主题。TVM已经被证明能够抵御新硬件目标的冲击,但直到现在,它还无法与微控制器的独特配置相抗衡。为了解决这个领域的问题,扩展了TVM,使其具有一个微控制器后端,称为µTVM(脚注:发音为“MicroTVM”)。µTVM有助于在裸机设备上执行tensor程序,并通过TVM的内置tensor程序优化器AutoTVM自动优化这些程序。下图显示了µTVM+AutoTVM基础设施的鸟瞰图:

让看看它的行动

在讨论什么是TVM/MicroTVM或它是如何工作之前,先看一个它在实际中的快速示例。

A standard µTVM setup, where the host communicates with the device via JTAG.

上面,有一个STM32F746ZG板,里面有一个ARM Cortex-M7处理器,考虑到它在低功耗封装中的强大性能,这是边缘人工智能的理想部件。使用它的USB-JTAG端口将其连接到台式机。在桌面上,运行OpenOCD来打开与设备的JTAG连接;反过来,OpenOCD允许µTVM使用与设备无关的TCP套接字控制M7处理器。有了这个设置,可以使用TVM代码运行CIFAR-10分类器,如下所示(此处为完整脚本):

OPENOCD_SERVER_ADDR = '127.0.0.1'

OPENOCD_SERVER_PORT = 6666

TARGET = tvm.target.create('c -device=micro_dev')

DEV_CONFIG = stm32f746xx.default_config(OPENOCD_SERVER_ADDR, OPENOCD_SERVER_PORT)

module, params = get_cifar10_cnn()

with micro.Session(device_config) as sess:

graph, c_module, params = relay.build(module['main'], target=TARGET, params=params)

micro_mod = micro.create_micro_mod(c_module, DEV_CONFIG)

graph_mod = graph_runtime.create(graph, micro_mod, ctx=tvm.micro_dev(0))

graph_mod.run(data=data_np)

prediction = CIFAR10_CLASSES[np.argmax(graph_mod.get_output(0).asnumpy())]

print(f'prediction was {prediction}')

下面是MicroTVM的性能结果,与CMSIS-NN版本5.7.0(commit a65b7c9a)相比,后者是一个手工优化的ML内核库。

开箱即用的性能不是很好,但这正是AutoTVM的救命稻草。可以为设备编写一个调度模板,进行一轮自动调整,然后获得显著更好的结果。要插入自动调谐结果,只需要替换这一行:

graph, c_module, params = relay.build(module['main'], target=TARGET, params=params)

with these lines:

with TARGET, autotvm.apply_history_best(TUNING_RESULTS_FILE):

graph, c_module, params = relay.build(module['main'], target=TARGET, params=params)

And our results now look like this:

性能提高了约2倍,现在离CMSIS-NN更近了。尽管MicroTVM CIFAR10的实现与类似的TFLite/CMSIS-NN模型相比具有竞争力,但这项工作刚刚开始利用TVM的优化特性。通过加速其他运营商(如密集/全连接dense/fully-connected)和利用TVM的模型特定量化和运算符融合功能,还有进一步优化的空间。带有µTVM的TVM能够发挥最佳性能。如何工作的呢?幕后是怎么回事?现在就开始吧。

Design

The µTVM Device Memory Layout in RAM

µTVM旨在通过最小化必须满足的一组要求来支持设备的最低公分母。特别是,用户只需提供:

  1. 设备的C交叉编译器工具链
  2. 一种读/写设备存储器并在设备上执行代码的方法
  3. 包含设备内存布局和一般体系结构特征的规范
  4. 为设备准备函数执行的代码段

大多数裸机设备都支持C和JTAG(调试协议),所以(1)和(2)通常是免费的!此外,(3)和(4)通常是非常小的要求。以下是STM32F746系列板的(3)和(4)示例。

device_config = {
    'device_id': 'arm.stm32f746xx',        # unique identifier for the device
    'toolchain_prefix': 'arm-none-eabi-',  # prefix of each binary in the cross-compilation toolchain (e.g., arm-none-eabi-gcc)
    'base_addr': 0x20000000,               # first address of RAM
    'section_sizes': {                     # dictionary of desired section sizes in bytes
         'text': 18000,
         'rodata': 100,
         'data': 100,
         ...
    },
    'word_size': 4,                        # device word size
    'thumb_mode': True,                    # whether to use ARM's thumb ISA
    'comms_method': 'openocd',             # method of communication with the device
    'server_addr': '127.0.0.1',            # OpenOCD server address (if 'comms_method' is 'openocd')
    'server_port': 6666,                   # OpenOCD server port (if 'comms_method' is 'openocd')
}
.syntax unified
.cpu cortex-m7
.fpu softvfp
.thumb
 
.section .text.UTVMInit
.type UTVMInit, %function
UTVMInit:
  /* enable fpu */
  ldr r0, =0xE000ED88
  ldr r1, [r0]
  ldr r2, =0xF00000
  orr r1, r2
  str r1, [r0]
  dsb
  isb
  /* set stack pointer */
  ldr sp, =_utvm_stack_pointer_init
  bl UTVMMain
.size UTVMInit, .-UTVMInit

µTVM基础架构和设备runtime的构建仅仅是为了利用这些需求,正在努力通过支持常见的开源runtime平台(如mBED OS)来处理编译和链接过程来减少这些需求。

设备会话

考虑到微控制器交互的网络特性,引入微会话的概念,稍微偏离了标准的TVM代码。

µTVM中的每一项功能都依赖于与目标设备的开放会话。如果熟悉TVM,可能已经注意到在第一个代码片段中有一行代码偏离了规范,即这一行:

...

with micro.Session(device_config) as sess:

...

此with块内的每一行都可以调用µTVM中的函数,上下文是device_config指定的设备。这条线在hood下面做了很多事情,让把它拆开。

首先,它使用指定的任何通信方法(通常是OpenOCD)初始化与设备的连接。然后使用指定的交叉编译器交叉编译µTVM设备 runtime。最后,主机为编译后的二进制文件分配空间,并使用打开的连接将二进制文件加载到设备上。

由于 runtime现在位于设备上,自然需要一些函数来运行它。

Module Loading

TVM的核心抽象之一是模块。模块为特定的设备/ runtime目标存储一组相关函数。考虑到微控制器通常没有操作系统,µTVM需要做大量额外的工作来维护这种高级抽象。为了了解发生了什么,将跟踪创建和加载µTVM兼容模块的过程。

假设有一个微型会议打开设备和实现二维卷积的TVM调度。如果想把它加载到微控制器上,需要它发出C代码。要做到这一点,只需要设定目标tvm.build or relay.build. Example:

graph, c_module, params = relay.build(module['main'], target='c -device=micro_dev', params=params)

通过这样设置目标,构建过程将通过C代码生成后端运行。但是,生成的C模块仍然驻留在主机上。为了将其加载到设备上,通过µTVM基础设施中的一个核心功能来运行它:create_micro_mod。

例子:

micro_mod = micro.create_micro_mod(c_module, DEV_CONFIG)

上面的行交叉编译模块中的C源代码,为生成的二进制文件分配空间(这样它就可以与设备内存中的 runtime共存),然后将二进制文件的每个部分发送到设备上分配的插槽中。一旦模块二进制文件在设备内存中处于合适的位置,二进制文件中的函数指针将被修补,使模块能够在设备 runtime访问help函数(例如,分配草稿行)。

现在,在设备上加载内核后,可以获取卷积函数的远程句柄,如下所示:

micro_func = micro_mod['conv2d']

Tensor Loading

If we want to call an operator, we first need some tensors as arguments:

data_np, kernel_np = get_conv_inputs()

ctx = tvm.micro_dev(0)

data = tvm.nd.array(data_np, ctx=ctx)

kernel = tvm.nd.array(kernel_np, ctx=ctx)

根据其数据类型(例如int8、float32等)和形状,计算每个张量的字节大小,主机在设备堆上分配内存区域。然后将张量的数据加载到分配的区域中。

函数调用

算子执行可能是这个系统中最棘手的部分。为了简化它的表示,将首先讨论严格执行(算子一被调用就立即执行),然后是延迟执行(只有在需要算子的结果时才执行算子)——后者是系统的实际工作方式。

严格执行

调用函数时,输入和输出张量都作为参数传递,这就是所谓的目标传递样式:

conv2D(data, kernel, output)

考虑到这些张量已经在设备上分配,只需要向设备发送元数据(device address, shape, and data type)(设备地址、形状和数据类型),这样设备就知道要使用哪个驻留张量。下面显示的是一个名为“runtime”的函数的调用。在构造这个表示之前,需要将元数据序列化到专门为此目的而存在的设备上的arguments部分中。

/*

 * task struct for uTVM

 */

typedef struct {

/* pointer to function to call for this task */

int32_t (*func)(void*, void*, int32_t);

/* array of argument tensors */

TVMValue* arg_values;

/* array of datatype codes for each argument */

int* arg_type_codes;

/* number of arguments */

int32_t num_args;

} UTVMTask;

在严格的设置中,有一个全局UTVMTask实例,从主机端写入该实例。一旦写入任务,runtime就拥有了执行函数所需的一切,可以在runtime的入口点开始执行。runtime将执行一些轻量级初始化,运行算子,然后将控制权返回给主机。

TinyML-TVM是如何驯服Tiny的(上)的更多相关文章

  1. TinyML-TVM是如何驯服Tiny的(下)

    TinyML-TVM是如何驯服Tiny的(下) Lazy Execution实际上,随着通信开销开始占主导地位,一旦用户请求,就执行算子的开销变得非常昂贵.可以通过延迟评估直到用户需要调用的结果来提高 ...

  2. 端到端TVM编译器(上)

    端到端TVM编译器(上) 摘要 将机器学习引入到各种各样的硬件设备中.AI框架依赖于特定于供应商的算子库,针对窄范围的服务器级gpu进行优化.将工作负载部署到新平台,例如手机.嵌入式设备和加速器(例如 ...

  3. TVM代码生成codegen

    TVM代码生成codegen 硬件后端提供程序(例如Intel,NVIDIA,ARM等),提供诸如cuBLAS或cuDNN之类的内核库以及许多常用的深度学习内核,或者提供框架例,如带有图形引擎的DNN ...

  4. 部署TVM Runtime

    部署TVM Runtime本文主要介绍如何在开发板上部署TVM Runtime, 在本地机器安装完整的TVM(包含了TVM Runtime以及编译功能), 并且使用一个简单的远程调用例子测试是否部署成 ...

  5. 端到端TVM编译器(下)

    端到端TVM编译器(下) 4.3 Tensorization DL工作负载具有很高的运算强度,通常可以分解为张量运算符,如矩阵乘法或一维卷积.这些自然分解导致了最近的添加张量计算原语.这些新的原语带来 ...

  6. TVM Pass IR如何使用

    TVM Pass IR如何使用 随着Relay / tir中优化遍数的增加,执行并手动维护其依赖关系变得很棘手.引入了一个基础结构来管理优化过程,并应用于TVM堆栈中IR的不同层. Relay / t ...

  7. 如何使用TVM Pass红外线

    如何使用TVM Pass红外线 随着Relay / tir中优化遍数的增加,执行并手动维护其依赖关系变得很棘手.引入了一个基础结构来管理优化过程,将其应用于TVM堆栈中IR的不同层. Relay / ...

  8. TVM自定义数据类型

    TVM自定义数据类型 本文将介绍"自定义数据类型"框架,该框架可在TVM中使用自定义数据类型. 介绍 在设计加速器时,关键是如何近似地表示硬件中的实数.这个问题具有长期的行业标准解 ...

  9. 将代码生成器带入TVM

    将代码生成器带入TVM 为了使数据科学家不必担心开发新模型时的性能,硬件后端提供程序(例如Intel,NVIDIA,ARM等)可以提供诸如cuBLAS或cuDNN之类的内核库以及许多常用的深度学习内核 ...

随机推荐

  1. tp5 composer phpexcel使用方法

    1.compser 安装phpexcel.在windows命令行下输入:进入网站根目录,compser phpoffice/phpexcel 2.页面引入两个类: use PHPExcel_IOFac ...

  2. 内网穿透工具FRP的使用

    目录 FRP 使用FRP建立隧道 服务端 客户端

  3. ListView 加载数据时 触摸报错

    问题起因: 在做一个从sd卡中加载数据显示在ListView中,由于数据可能比较多,考虑到用户体验,就使用AsyncTask来异步加载,数据一条一条的添加至ListView中. 开始数据比较少的时候, ...

  4. android 代码中使用textAppearance

    一开始在代码中我以为使用tvAge.setTextAppearance(context, resid);这样的的方式就能行, 运行之后发现这个设置并未生效,于是到处搜索在代码中设置系统样式的的解决方法 ...

  5. <JVM上篇:内存与垃圾回收篇>02-类加载子系统

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  6. 【python】Leetcode每日一题-删除排序链表中的重复元素2

    [python]Leetcode每日一题-删除排序链表中的重复元素2 [题目描述] 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表 ...

  7. Day003 变量、常量、作用域

    变量 变量:就是可以变化的量 Java是一种强类型语言,每个变量都必须声明其类型. Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域 变量的定义 数据类型 变量名 = 值:可以 ...

  8. MySQL|一文解决主库已有数据的主从复制

    主从复制配置方案和实际的场景有很多,在之前配置了主从库都是全新的配置方案 在这一篇会配置主库存在数据,然后配置主从复制 开始之前,先分享一套MySQL教程,小白入门或者学习巩固都可以看 MySQL基础 ...

  9. MySQL修改账号密码方法大全

    前言: 在日常使用数据库的过程中,难免会遇到需要修改账号密码的情景,比如密码太简单需要修改.密码过期需要修改.忘记密码需要修改等.本篇文章将会介绍需要修改密码的场景及修改密码的几种方式. 1.忘记 r ...

  10. Spring Cloud Alibaba(11)---Sentinel+Nacos持久化

    Sentinel+Nacos持久化 有关Sentinel之前有写过两篇 Spring Cloud Alibaba(9)---Sentinel概述 Spring Cloud Alibaba(10)--- ...