Welcome to Tengine’s documentation!

请在页面左下角选择特定版本的文档。

技术亮点

TengineOPEN AI LAB 主导开发,该项目实现了深度学习神经网络模型在嵌入式设备上的快速高效部署需求。为实现在众多 AIoT 应用中的跨平台部署,本项目基于原有 Tengine 项目使用 C 语言进行重构,针对嵌入式设备资源有限的特点进行了深度框架裁剪。同时采用了完全分离的前后端设计,有利于 CPU、GPU、NPU 等异构计算单元的快速移植和部署,同时降低评估和迁移成本。

多硬件支持

Tengine 支持多种硬件后端对神经网络模型进行加速推理,包含 CPU(ARM、X86、MIPS、RISC-V)、GPU(Mail、NV、AMD、Adreno、PowerVR)、NPU(VSI、NNIE、DLA)。

高性能

通过提供计算图优化(算子合并、算子移除),实现对原生网络模型结构进行优化,降低计算量。

同时针对不同 CPU 架构,采用精细的手工汇编,实现对计算量要求较高的 Kernel 进行极致优化,充分发挥硬件峰值算力。

异构切图

为了支持不同 SoC 上多种计算单元,Tengine 在加载模型后,获取当前指定硬件加速特性,灵活切分原有计算图,实现算力充分利用,同时提高模型支持的泛化性。

量化支持

支持主流的两种量化策略(对称分通道量化、非对称分层量化)实现模型低比特压缩、性能加速的目的,同时做到无缝对接主流 NPU 加速引擎。

提供低比特量化精度补偿方案。

混合精度

为了充分发挥硬件计算资源,同时保证模型推理精度,支持混合精度计算模式。

轻量级部署

最新的 Tengine 核心模块代码采用 C 语言开发,无第三方库依赖,最小可执行静态库大小 < 100KB,甚至可在主流 MCU 上进行模型部署。

架构设计

Tengine 架构如下图

_images/architecture.pngTengine 架构图

支持硬件

ARM CPU

Tengine 已完成对下列 ARM Cortex-A 系列处理器支持:

32bit

  • Cortex-A5

  • Cortex-A7

  • Cortex-A8

  • Cortex-A9

  • Cortex-A12

  • Cortex-A15

  • Cortex-A17

  • Cortex-A32

64bit

  • Cortex-A53

  • Cortex-A55

  • Cortex-A57

  • Cortex-A72

  • Cortex-A73

  • Cortex-A75

  • Cortex-A76

端侧 GPU

通过 ACL 已验证 ARM Mali-GPU 以下系列:

  • T860

  • G31

  • G52

通过 CUDA/TensorRT 已验证 NVIDIA 以下系列:

  • Jetson NANO

  • Jetson TX1

  • Jetson TX2

  • Jetson XAVIER NX

  • Jetson XAVIER AGX

通过 OpenCL 已验证以下型号:

  • 测试中

通过 Vulkan 已验证以下型号:

  • 测试中

服务器 GPU

通过 CUDA/TensorRT 已验证 NVIDIA 以下系列:

  • QUADRO RTX 8000

  • GeForce RTX 3090

  • GeForce GTX 1080Ti

NPU

通过 TIM-VX 已验证内置 VeriSilicon VIP8000/VIP9000 NPU 的以下芯片:

  • A311D

  • S905D3

  • iMX.8MP

  • JLQ

算子支持

Tengine Caffe MXNet TensorFlow TF-Lite ONNX
Accuracy
BatchNormalization BatchNorm BatchNorm FusedBatchNorm
ComposedBN
Resize RESIZE_NEAREST_NEIGHBOR
Concat ConcatV2 CONCATENATION
Const
Convolution Conv2D CONV_2D Conv
DepthwiseConvolution DepthwiseConv2dNative DEPTHWISE_CONV_2D
ConvolutionDepthwise
Deconvolution Conv2DBackpropInput
DetectionOutput
Dropout Copy
Eltwise _minus_scalar Add ADD Add
_mul_scalar Sub SUB Sub
elemwise_add PROD
Rsqrt RSQRT
_div_scalar RealDiv DIV Div
Log LOG
Exp EXP Exp
Pow POW
Sqrt SQRT
Floor FLOOR Floor
Mul MUL Mul
Minimum
AddN
Flatten
FullyConnected InnerProduct MatMul FULLY_CONNECTED Gemm
Input Data FIFOQueueV2
Input
LRN
Normalize
Permute transpose
Pooling AvgPool AVERAGE_POOL_2D AveragePool
GlobalAveragePool
MaxPool MAX_POOL_2D MaxPool
PReLU LeakyReLU PRelu
PriorBox
Region
ReLu Activation Relu Relu
LeakyReLU LeakyRelu
ReLu6 clip Relu6
Reorg
Reshape RESHAPE
ROIPooling
RPN
Scale
Slice
Softmax Activation SOFTMAX
SoftmaxWithLoss
SoftmaxOutput
SoftmaxActivation
Split
DetectionPostProcess TFLite_Detection_PostProcess
Gemm
Generic DecodeWav
AudioSpectrogram
Mfcc
Logistic LOGISTIC
LSTM RNN
RNN
Tanh TanH Activation
Sigmoid Activation
Squeeze SQUEEZE
Pad
MirrorPad
StridedSlice STRIDED_SLICE
Reduction Sum SUM
Mean MEAN
Asum
Sqsum
Max
Min
Prod
L2
Logsum
Logsumexp
ArgMax
ArgMin
TopKV2
Maximum
Minimum
Addn add_n
SwapAxis
GRU RNN
Upsample UpSampling
ShuffleChannel
Resize ResizeNearestNeighbor
ResizeBilinear
SpaceToBatchND
BatchToSpaceND
Crop
Psroipooling _contrib_PSROIPooling
Roialign _contrib_ROIAlign
Expanddims ExpandDims
Unary
abs Abs
neg Neg
ceil Ceil
floor Floor
sin Sin
Asin
cos Cos
Acos
atan Atan
tan Tan
reciprocal Reciprocal
Square
Sqrt
Rsqrt
Exp
Log
Bias
Noop
Threshold
Hardsigmoid
Embedding
InstanceNorm
MVN
Absval
Cast
HardSwish
Interp UpSampling Upsample
Selu
Elu LeakyReLU ELU
BroadMul broadcast_mul
Logical LOGICALOR
LOGICALAND
Gather GATHER
Transpose TRANSPOSE
Comparison Equal EQUAL
Greater GREATER
GreaterEqual GREATER_EQUAL
Less LESS
LessEqual
LESS_GREATER
SpaceToDepth SPACE_TO_DEPTH
DepthToSpace DEPTH_TO_SPACE
Reverse ReverseV2 REVERSE_V2
SparseToDense SPARSE_TO_DENSE
Ceil CEIL
SquaredDifference SQUARED_DIFFERENCE
Round ROUND
ZerosLike
Clip Clip Clip
Power Power
Tile Tile
L2Normalization L2_NORMALIZATION
L2Pool L2_POOL_2D
Relu1 RELU_N1_TO_1
LogSoftmax LOG_SOFTMAX
Floor Floor

模型支持

Tengine 已完成对主流的计算机视觉模型的进行支持,包括分类、检测、识别、分割、关键点、OCR等功能。

  • 支持的CPU架构:ARM、X86、MIPS、RISC-V ;

  • 支持的GPU架构:NVIDIA valta TensorCore、Adreno、Mali ;

  • 支持的NPU架构:NPU ;

类别 模型 支持平台
分类 MobileNet V1 CPU、GPU、NPU
检测 MobileNet-SSD CPU、GPU、NPU
识别 MobileFaceNets CPU、GPU
检测 YOLOv3 CPU、GPU、NPU
检测 YOLOv3-Tiny CPU、GPU、NPU
检测 YOLOv4 CPU、GPU、NPU
检测 YOLOv4-Tiny CPU、GPU、NPU
检测 YOLOv5 CPU、GPU、NPU
检测 YOLOv5s CPU、GPU、NPU
检测 YOLOvfastest CPU、GPU、NPU
人脸 retinaface CPU、GPU、NPU
人脸 ultraface CPU、GPU
分割 YOLCAT CPU、GPU
关键点 Landmark CPU、GPU
关键点 Alphapose CPU、GPU
关键点 Openpose CPU、GPU
OCR crnn_lite_dense CPU、GPU

提示

  • 模型链接来自 Tengine Model Zoo,我们将持续更新;

  • 支持平台列表中的 NPU 中,部分模型采用异构计算实现,即 CPU+NPU。

模型仓库

测试方法

Benchmark 是评估目标硬件平台网络模型运行速度的简单途径,只依赖于网络结构(xxx_benchmark.tmfile)即可。

测试模型获取

虽然可以直接使用完整的 tmfile 运行 benchmark 示例,但是我们建议采用 benchmark 专用 tmfile 模型,节省文件传输时间。

  • 使用模型转换工具转换前,设置以下环境变量,将生成不带参数的 tmfile 文件,专门用于 benchmark 测试。

$ export TM_FOR_BENCHMARK=1
  • 将原始框架模型转换为 tmfile benchmark 专用模型,以 Caffe 框架的 mobilenet_v1 举例:

$ ./convert_tm_tool -f caffe -p mobilenet_v1.prototxt -m mobilenet_v1.caffemodel -o mobilenet_v1_benchmark.tmfile

我们已经提前转换了一小部分评估模型在 benchmark/models 中。

获取

默认完成 Tengine 编译,目标平台的 benchmark 可执行程序存放在 build/install/bin/tm_benchmark

使用方法

$ ./tm_benchmark -h
[Usage]:  [-h] [-r repeat_count] [-t thread_count] [-p cpu affinity, 0:auto, 1:big, 2:middle, 3:little] [-s net]
khadas@Khadas:~/tengine-lite/benchmark$ ../build/benchmark/tm_benchmark -r 5 -t 1 -p 1
start to run register cpu allocator
loop_counts = 5
num_threads = 1
power       = 1
tengine-lite library version: 1.0-dev
     squeezenet_v1.1  min =   55.66 ms   max =   56.19 ms   avg =   56.04 ms
         mobilenetv1  min =  103.18 ms   max =  105.37 ms   avg =  104.26 ms
         mobilenetv2  min =   91.46 ms   max =   93.07 ms   avg =   91.92 ms
         mobilenetv3  min =   56.30 ms   max =   57.17 ms   avg =   56.64 ms
        shufflenetv2  min =   29.92 ms   max =   30.62 ms   avg =   30.29 ms
            resnet18  min =  162.31 ms   max =  162.74 ms   avg =  162.48 ms
            resnet50  min =  495.61 ms   max =  498.00 ms   avg =  496.99 ms
           googlenet  min =  199.16 ms   max =  200.32 ms   avg =  199.72 ms
         inceptionv3  min =  801.93 ms   max =  813.71 ms   avg =  807.08 ms
               vgg16  min =  866.41 ms   max =  877.53 ms   avg =  871.45 ms
                mssd  min =  204.10 ms   max =  208.92 ms   avg =  206.05 ms
          retinaface  min =   28.57 ms   max =   29.06 ms   avg =   28.86 ms
         yolov3_tiny  min =  233.68 ms   max =  235.12 ms   avg =  234.19 ms
      mobilefacenets  min =   44.32 ms   max =   44.82 ms   avg =   44.60 ms
ALL TEST DONE

Tengine 推理流程

依照顺序调用Tengine核心API如下:

1. init_tengine

初始化Tengine,该函数在程序中只要调用一次即可。

2. create_graph

创建Tengine计算图。

3. prerun_graph

预运行,准备计算图推理所需资源。设置大小核,核个数、核亲和性、数据精度都在这里。

struct options
{
  int num_thread;//核个数设置,
  int cluster;//大小核设置,可选TENGINE_CLUSTER_[ALL,BIG,MEDIUM,LITTLE]
  int precision;//精度设置,TENGINE_MODE_[FP32,FP16,HYBRID_INT8,UINT8,INT8]
  uint64_t affinity;//核亲和性掩码,绑定具体核,
};

4. run_graph

启动Tengine计算图推理。

5. postrun_graph

停止运行graph,并释放graph占据的资源。

6. destroy_graph

销毁graph。

postrun_graph和destroy_graph在执行完模型推理后调用,一般是连续调用。 使用markdown流程图mermaid表示如下:

graph TD
	A(init_tengine)
	i1(image)-->A
	i2(model)-->A
	i3(paramaters)-->A
	A --> B[create_graph]
    B --> C[prerun_graph]
    C --> D[run_graph]
    D --> E[postrun_graph]
    E --> F(destory_graph)
    F --> O(预测结果)
img
图1 推理流程图

示例展示

本章节展示的所有示例位于examples

准备工作

环境准备

要编译和运行示例程序,你需要准备:

1.一台可以编译C/C++ 的Linux环境的电脑(x86或Arm架构均可)。

2.下载Tengine源码,位于 Tengine 的分支 tengine-lite 上:

git clone -b tengine-lite https://github.com/OAID/Tengine.git  Tengine

编译

build.sh 编译脚本默认配置已实现自动编译 examples 中的 demo 程序。

以x86架构为例,编译后demo 存放在 ./build/install/bin/ 目录下:

bug1989@DESKTOP-SGN0H2A:/mnt/d/ubuntu/gitlab/build-linux$ tree install
install
├── bin
│   ├── tm_alphapose
│   ├── tm_classification
│   ├── tm_classification_int8
│   ├── tm_classification_uint8
│   ├── tm_crnn
│   ├── tm_efficientdet
│   ├── tm_efficientdet_uint8
│   ├── tm_hrnet
│   ├── tm_landmark
│   ├── tm_landmark_uint8
│   ├── tm_mobilefacenet
│   ├── tm_mobilefacenet_uint8
│   ├── tm_mobilenet_ssd
│   ├── tm_mobilenet_ssd_uint8
│   ├── tm_nanodet_m
│   ├── tm_openpose
│   ├── tm_retinaface
│   ├── tm_ultraface
│   ├── tm_unet
│   ├── tm_yolact
│   ├── tm_yolact_uint8
│   ├── tm_yolofastest
│   ├── tm_yolov3
│   ├── tm_yolov3_tiny
│   ├── tm_yolov3_tiny_uint8
│   ├── tm_yolov3_uint8
│   ├── tm_yolov4
│   ├── tm_yolov4_tiny
│   ├── tm_yolov4_tiny_uint8
│   ├── tm_yolov4_uint8
│   ├── tm_yolov5
│   └── tm_yolov5s
├── include
│   └── tengine
│       └── c_api.h                     C预测库头文件
└── lib
    ├── libtengine-lite-static.a        静态预测库
    └── libtengine-lite.so              动态预测库

模型仓库

模型仓库包含了运行examples所需模型、图片和文档。

分类任务 - tm_classification.c

Tengine Lite 兼容 Tengine 原有的 C API 供用户使用,这里我们使用 C API 展示如何运行 tm_classification 例程运行 MobileNet v1 分类网络模型,实现指定图片分类的功能。让你快速上手 Tengine Lite C API。这里,我们使用在这个撸猫时代行业从业者大爱的 tiger cat 作为测试图片。

https://z3.ax1x.com/2021/06/30/RBIQIO.jpg

将测试图片和模型文件放在 Tengine-Lite 根目录下,运行:

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_classification -m models/mobilenet.tmfile -i images/cat.jpg -g 224,224 -s 0.017,0.017,0.017 -w 104.007,116.669,122.679

结果如下:

tengine-lite library version: 1.4-dev

model file : models/mobilenet.tmfile
image file : images/cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 33.74 ms, max_time 33.74 ms, min_time 33.74 ms
--------------------------------------
8.574144, 282
7.880117, 277
7.812573, 278
7.286458, 263
6.357486, 281
--------------------------------------

人脸关键点检测任务 - tm_landmark.cpp

使用图片:

https://z3.ax1x.com/2021/06/30/RB5dC4.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_landmark -m models/landmark.tmfile -i images/mobileface02.jpg -r 1 -t 1

结果如下:

tengine-lite library version: 1.4-dev
Repeat [1] min 8.784 ms, max 8.784 ms, avg 8.784 ms

https://z3.ax1x.com/2021/07/01/RrPSuq.jpg

retinaface 人脸检测任务 - tm_retinaface.cpp

使用图片:

https://z3.ax1x.com/2021/06/30/RBC311.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_retinaface -m models/retinaface.tmfile -i images/mtcnn_face4.jpg -r 1 -t 1

结果如下:

tengine-lite library version: 1.4-dev
img_h, img_w : 316, 474
Repeat 1 times, thread 1, avg time 28.78 ms, max_time 28.78 ms, min_time 28.78 ms
--------------------------------------
detected face num: 4
BOX 1.00:( 38.4053 , 86.142 ),( 46.3009 , 64.0174 )
BOX 0.99:( 384.076 , 56.9844 ),( 76.968 , 83.9609 )
BOX 0.99:( 169.196 , 87.1324 ),( 38.4133 , 46.8504 )
BOX 0.98:( 290.004 , 104.453 ),( 37.6346 , 46.7777 )

https://z3.ax1x.com/2021/07/01/Rrs6LF.jpg

yolact 实例分割任务 - tm_yolact.cpp

使用图片:

https://z3.ax1x.com/2021/06/30/RBFpTO.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_yolact -m models/yolact.tmfile -i images/ssd_car.jpg -r 1 -t 1

结果如下:

tengine-lite library version: 1.4-dev
Repeat 1 times, thread 1, avg time 2064.44 ms, max_time 2064.44 ms, min_time 2064.44 ms
--------------------------------------
6 = 0.99966 at 130.82 57.77 340.78 x 237.36
3 = 0.99675 at 323.39 194.97 175.57 x 132.96
1 = 0.33431 at 191.24 195.78 103.06 x 179.22

https://z3.ax1x.com/2021/07/01/RrEbEq.jpg

unet 图像分割任务 - tm_unet.cpp

使用图片:

https://z3.ax1x.com/2021/07/01/Rse0SK.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_unet -m models/unet_sim3.tmfile -i images/carvana01.jpg -r 1 -t 1

结果如下:

Image height not specified, use default 512
Image width not specified, use default  512
Scale value not specified, use default  0.00392, 0.00392, 0.00392
tengine-lite library version: 1.4-dev

model file : models/unet_sim3.tmfile
image file : images/carvana01.jpg
img_h, img_w, scale[3], mean[3] : 512 512 , 0.004 0.004 0.004, 0.0 0.0 0.0
Repeat 1 times, thread 1, avg time 4861.93 ms, max_time 4861.93 ms, min_time 4861.93 ms
--------------------------------------
segmentatation result is save as unet_out.png

https://z3.ax1x.com/2021/07/01/Rs8YjI.png

yolov5s目标检测任务 - tm_yolov5s.cpp

使用图片:

https://z3.ax1x.com/2021/06/30/RBVdq1.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_yolov5s -m models/yolov5s.tmfile -i images/ssd_dog.jpg -r 1 -t 1

结果如下:

tengine-lite library version: 1.4-dev
Repeat 1 times, thread 1, avg time 462.94 ms, max_time 462.94 ms, min_time 462.94 ms
--------------------------------------
detection num: 3
16:  89%, [ 135,  218,  313,  558], dog
 7:  86%, [ 472,   78,  689,  169], truck
 1:  75%, [ 123,  107,  578,  449], bicycle

https://z3.ax1x.com/2021/06/30/RBl7Wt.jpg

hrnet人体姿态识别任务 - tm_hrnet.cpp

使用图片:

https://s1.ax1x.com/2020/09/01/dvJm8A.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_hrnet -m models/hrnet.tmfile -i images/pose.jpg -r 1 -t 1

结果如下:

tengine-lite library version: 1.4-dev
Repeat [1] min 416.223 ms, max 416.223 ms, avg 416.223 ms
x: 27, y: 58, score: 0.91551
x: 27, y: 45, score: 0.865156
x: 28, y: 30, score: 0.831916
x: 34, y: 29, score: 0.839507
x: 38, y: 44, score: 0.88559
x: 35, y: 55, score: 0.891349
x: 31, y: 30, score: 0.873104
x: 31, y: 14, score: 0.928233
x: 30, y: 10, score: 0.948434
x: 29, y: 1, score: 0.915752
x: 23, y: 31, score: 0.811694
x: 24, y: 24, score: 0.935574
x: 24, y: 14, score: 0.899991
x: 37, y: 13, score: 0.908696
x: 41, y: 22, score: 0.902927
x: 41, y: 29, score: 0.847032

https://z3.ax1x.com/2021/07/01/RrSvg1.jpg

汉字识别任务 - tm_crnn.cpp

模型文件:crnn_lite_dense.tmfile 测试图片:o2_resize.jpg 字库文件:keys.txt 测试图片:

https://s1.ax1x.com/2020/10/20/BSlFPS.jpg

$ export LD_LIBRARY_PATH=./build/install/lib
$ ./build/install/bin/tm_crnn -m models/crnn_lite_dense.tmfile -i images/o2_resize.jpg -l files/keys.txt

结果如下:

tengine-lite library version: 1.4-dev
Repeat 1 times, thread 1, avg time 23.30 ms, max_time 23.30 ms, min_time 23.30 ms
--------------------------------------
如何突破自己的颜值上限
--------------------------------------

其中ocr的识别结果会直接打印到终端中, 同时如果需要保存为txt文件可以修改源码使其重定向到文件。

我们将持续更新各种有趣的 demo ,敬请期待……

模型转换工具

Tengine Convert Tool 支持将多种训练框架模型转换成 Tengine 推理框架适配的模型格式 tmfile。最新版本已支持以下框架模型:

  • Caffe

  • MXNet

  • PyTorch(ONNX)

  • TensorFlow

  • TFLite

  • Darknet

  • MegEngine

  • OneFlow

  • PaddlePalle 2.0

同时 Tengine Convert Tool 还支持将其他优秀的端侧框架模型转换成 Tengine 推理框架适配的模型格式 tmfile。最新版本已支持以下框架模型:

  • ncnn

依赖库安装

sudo apt install libprotobuf-dev protobuf-compiler

源码编译

mkdir build && cd build
cmake ..
make -j`nproc` && make install

编译完成后,生成的可执行文件 tm_convert_tool 存放在 ./build/install/bin/ 目录下。

执行模型转换

  • 命令解析

$ ./tm_convert_tool -h
[Convert Tools Info]: optional arguments:
        -h    help            show this help message and exit
        -f    input type      path to input float32 tmfile
        -p    input structure path to the network structure of input model(*.prototxt, *.symbol, *.cfg)
        -m    input params    path to the network params of input model(*.caffemodel, *.params, *.weight, *.pb, *.onnx, *.tflite)
        -o    output model    path to output fp32 tmfile
  • Caffe

./tm_convert_tool -f caffe -p mobilenet.prototxt -m mobilenet.caffemodel -o mobilenet.tmfile
  • MXNet

./tm_convert_tool -f mxnet -p mobilenet.json -m mobilene.params -o mobileent.tmfile
  • ONNX

./tm_convert_tool -f onnx -m mobilenet.onnx -o mobilenet.tmfile
  • TensorFlow

./tm_convert_tool -f tensorflow -m mobielenet_v1_1.0_224_frozen.pb -o mobilenet.tmfile
  • TFLite

./tm_convert_tool -f tflite -m mobielenet.tflite -o mobilenet.tmfile
  • Darknet

./tm_convert_tool -f darknet -p yolov3.cfg -m yolov3.weights -o yolov3.tmfile
  • MegEngine

./tm_convert_tool -f megengine -m mobilenet.pkl -o mobilenet.tmfile
  • OneFlow

./tm_convert_tool -f oneflow -p mobilenet.prototxt -m mobilenet/ -o mobilenet.tmfile
  • ncnn

./tm_convert_tool -f ncnn -p mobilenet.param -m mobilenet.bin -o mobilenet.tmfile

模型量化-对称量化

为了支持在 AIoT 设备上部署 int8 模型,我们提供了一些通用的 post training quantization 工具,可以将 Float32 tmfile 模型转换为 int8 tmfile 模型。

对称分通道量化

Type Note
Adaptive TENGINE_MODE_INT8
Activation data Int8
Weight date Int8
Bias date Int32
Example tm_classification_int8.c
Execution environment Ubuntu 18.04

适配硬件

  • CPU Int8 mode

  • TensorRT Int8 mode

下载

当前我们提供预编译好的可执行文件, 您可以从这里获取 quant_tool_int8

安装依赖库

sudo apt install libopencv-dev

运行参数

$ ./quant_tool_int8 -h
[Quant Tools Info]: optional arguments:
-h    help            show this help message and exit
-m    input model     path to input float32 tmfile
-i    image dir       path to calibration images folder
-o    output model    path to output int8 tmfile
-a    algorithm       the type of quant algorithm(0:min-max, 1:kl, default is 1)
-g    size            the size of input image(using the resize the original image,default is 3,224,224
-w    mean            value of mean (mean value, default is 104.0,117.0,123.0
-s    scale           value of normalize (scale value, default is 1.0,1.0,1.0)
-b    swapRB          flag which indicates that swap first and last channels in 3-channel image is necessary(0:OFF, 1:ON, default is 1)
-c    center crop     flag which indicates that center crop process image is necessary(0:OFF, 1:ON, default is 0)
-y    letter box      flag which indicates that letter box process image is necessary(maybe using for YOLO, 0:OFF, 1:ON, default is 0)
-t    num thread      count of processing threads(default is 4)

示例

使用量化工具前, 你需要 Float32 tmfile 和 Calibration Dataset(量化校准数据集)

  • 校准数据内容,尽可能的覆盖该模型的所有应用场景,一般我们的经验是从训练集中随机抽取;

  • 校准数据张数,根据经验我们建议使用 500-1000 张。

$ .quant_tool_int8  -m ./mobilenet_fp32.tmfile -i ./dataset -o ./mobilenet_int8.tmfile -g 3,224,224 -w 104.007,116.669,122.679 -s 0.017,0.017,0.017

---- Tengine Post Training Quantization Tool ----

Version     : v1.0, 17:32:30 Dec 24 2020
Status      : int8, per-channel, symmetric
Input model : ./mobilenet_fp32.tmfile
Output model: ./mobilenet_int8.tmfile
Calib images: ./dataset
Algorithm   : KL
Dims        : 3 224 224
Mean        : 104.007 116.669 122.679
Scale       : 0.017 0.017 0.017
BGR2RGB     : ON
Center crop : OFF
Letter box  : OFF
Thread num  : 1

[Quant Tools Info]: Step 0, load FP32 tmfile.
[Quant Tools Info]: Step 0, load FP32 tmfile done.
[Quant Tools Info]: Step 0, load calibration image files.
[Quant Tools Info]: Step 0, load calibration image files done, image num is 55.
[Quant Tools Info]: Step 1, find original calibration table.
[Quant Tools Info]: Step 1, find original calibration table done, output ./table_minmax.scale
[Quant Tools Info]: Step 2, find calibration table.
[Quant Tools Info]: Step 2, find calibration table done, output ./table_kl.scale
[Quant Tools Info]: Thread 1, image nums 55, total time 1964.24 ms, avg time 35.71 ms
[Quant Tools Info]: Calibration file is using table_kl.scale
[Quant Tools Info]: Step 3, load FP32 tmfile once again
[Quant Tools Info]: Step 3, load FP32 tmfile once again done.
[Quant Tools Info]: Step 3, load calibration table file table_kl.scale.
[Quant Tools Info]: Step 4, optimize the calibration table.
[Quant Tools Info]: Step 4, quantize activation tensor done.
[Quant Tools Info]: Step 5, quantize weight tensor done.
[Quant Tools Info]: Step 6, save Int8 tmfile done, ./mobilenet_int8.tmfile

---- Tengine Int8 tmfile create success, best wish for your INT8 inference has a low accuracy loss...\(^0^)/ ----

模型量化-非对称量化

为了支持在 AIoT 设备上部署 uint8 模型,我们提供了一些通用的 post training quantization 工具,可以将 Float32 tmfile 模型转换为 int8 tmfile 模型。

非对称分层量化

Type Note
Adaptive TENGINE_MODE_UINT8
Activation data UInt8
Weight date UInt8
Bias date Int32
Example tm_classification_uint8.c
Execution environment Ubuntu 18.04

适配硬件

  • CPU Uint8 mode

  • TIM-VX NPU(such as A311D、i.MX8M Plus、RV1126?)

下载

当前我们提供预编译好的可执行文件, 您可以从这里获取 quant_tool_uint8.

安装依赖库

sudo apt install libopencv-dev

运行参数

$ ./quant_tool_uint8 -h
[Quant Tools Info]: optional arguments:
-h    help            show this help message and exit
-m    input model     path to input float32 tmfile
-i    image dir       path to calibration images folder
-o    output model    path to output int8 tmfile
-a    algorithm       the type of quant algorithm(0:min-max, 1:kl, default is 1)
-g    size            the size of input image(using the resize the original image,default is 3,224,224
-w    mean            value of mean (mean value, default is 104.0,117.0,123.0
-s    scale           value of normalize (scale value, default is 1.0,1.0,1.0)
-b    swapRB          flag which indicates that swap first and last channels in 3-channel image is necessary(0:OFF, 1:ON, default is 1)
-c    center crop     flag which indicates that center crop process image is necessary(0:OFF, 1:ON, default is 0)
-y    letter box      flag which indicates that letter box process image is necessary(maybe using for YOLO, 0:OFF, 1:ON, default is 0)
-t    num thread      count of processing threads(default is 4)

示例

使用量化工具前, 你需要 Float32 tmfile 和 Calibration Dataset(量化校准数据集)

  • 校准数据内容,尽可能的覆盖该模型的所有应用场景,一般我们的经验是从训练集中随机抽取;

  • 校准数据张数,根据经验我们建议使用 500-1000 张。

$ .quant_tool_uint8  -m ./mobilenet_fp32.tmfile -i ./dataset -o ./mobilenet_uint8.tmfile -g 3,224,224 -w 104.007,116.669,122.679 -s 0.017,0.017,0.017

---- Tengine Post Training Quantization Tool ----

Version     : v1.0, 18:06:10 Mar  4 2021
Status      : uint8, per-layer, asymmetric
Input model : ./mobilenet_fp32.tmfile
Output model: ./mobilenet_uint8.tmfile
Calib images: ./dataset
Algorithm   : MIN MAX
Dims        : 3 224 224
Mean        : 104.007 116.669 122.679
Scale       : 0.017 0.017 0.017
BGR2RGB     : ON
Center crop : OFF
Letter box  : OFF
Thread num  : 1

[Quant Tools Info]: Step 0, load FP32 tmfile.
[Quant Tools Info]: Step 0, load FP32 tmfile done.
[Quant Tools Info]: Step 0, load calibration image files.
[Quant Tools Info]: Step 0, load calibration image files done, image num is 55.
[Quant Tools Info]: Step 1, find original calibration table.
[Quant Tools Info]: Step 1, images 00055 / 00055
[Quant Tools Info]: Step 1, find original calibration table done, output ./table_minmax.scale
[Quant Tools Info]: Step 2, find calibration table.
[Quant Tools Info]: Step 2, images 00001 / 00055
[Quant Tools Info]: Step 2, find calibration table done, output ./table_kl.scale
[Quant Tools Info]: Thread 1, image nums 55, total time 1195.07 ms, avg time 21.73 ms
[Quant Tools Info]: Calibration file is using table_minmax.scale
[Quant Tools Info]: Step 3, load FP32 tmfile once again
[Quant Tools Info]: Step 3, load FP32 tmfile once again done.
[Quant Tools Info]: Step 3, load calibration table file table_minmax.scale.
[Quant Tools Info]: Step 4, optimize the calibration table.
[Quant Tools Info]: Step 4, quantize activation tensor done.
[Quant Tools Info]: Step 5, quantize weight tensor done.
[Quant Tools Info]: Step 6, save Int8 tmfile done, ./mobilenet_uint8.tmfile

---- Tengine Int8 tmfile create success, best wish for your INT8 inference has a low accuracy loss...\(^0^)/ ----

模型可视化工具

简介

Netron 是常用的机器学习模型可视化工具。

目的

适配 Netron 项目,使其支持解析 tmfile,可视化 Tengine 模型。

Tengine模型

Tengine 模型为后缀 ”.tmfile”文件,由 Tengine: Covert Tool 通过其他训练框架转换得到,存储数据格式为二进制。

原理介绍

  1. Netron 是基于 Node.js 开发的 Electron 应用程序,使用的语言是 javascript;

  2. Electron 应用程序是使用 javascript 开发的跨平台、应用程序框架;

  3. Netron 在解析模型文件后,读取到 a) 模型信息,Model Properties; b) 模型输入、输出,Model Inputs/Outputs,包含输入数据尺寸; c) 模型绘图,左侧显示模型结构; d) 节点信息,Node Properties,Attributes,Inputs, Outputs等;

Model Properties

进入Netron界面后,点左上角图标或点击灰色节点(如 图1 中红色标记所示),弹出右侧边栏:Model Properties。

img
图1 模型信息 Model Properties

(1) MODEL PROPERTIES

a) format:解析到 Tengine 模型文件时显示 Tengine V2.0; b) source: 源模型格式,如通过 Caffe 转换成 tmfile,则显示 Caffe;如通过TensorFlow 转换成 tmfile,则显示 TensorFlow;

(2) INPUTS

a) data:

name: 输入 tensor 的名称,如此处为 data; type: 数据类型,此处为 FP32 格式;维度信息,此模型为 [10,3,227,227];

(3) OUTPUTS

a) prob:

name: 输出 tensor 的名称,如此处为 prob; type: 数据类型,此处为 FP32 格式;维度信息位置,须经过 infershape 后由 Tengine 计算得到输出尺寸。

模型绘图

Tengine 中,模型通过 tensor 连接。

节点 Node 连线形成网络,并根据不同算子类型显示不同颜色。如 ”layer” 类型节点显示为蓝色,”Activation” 相关节点显示为深红色,”Normalize” 相关节点显示为深绿色。 Convolution 算子默认显示 weight 和 bias 维度信息。

img
图2 模型绘图

节点信息

节点为 Node,每个节点包含一个算子 Operator。

算子具有类型type、名称name、属性ATTRIBUTES及输入INPUTS、输出OUTPUTS。

img
图2 模型绘图

点击绘图区的Node,右侧弹出该节点的详细信息,其中包括:

(1) NODE PROPERTIES:

a) type: 算子类型,如Convolution算子则显示Convolution;

b) name: 节点名称,如节点名为conv-relu_conv1(绘图区被选中、红色标记的Convolution节点);

(2) ATTRIBUTIES: 有参数的算子会显示,无参数的算子不显示;根据算子类型的不同,显示不同的ATTRIBUTES列表;如【5 不同算子的Attributes】根据不同算子类型有详细列表。

(3) INPUTS: 显示该节点的输入,其中:

a) input:显示输入tensor名称,即前一个 Node 的输出;

b) weight/bias/…:为输入的其他参数,如 weight,bias等。在Tengine中,weight、bias等作为 Node,以输出 tensor 的形式,传递数据给其对应的节点。

(4) OUTPUTS: 输出tensor:

此处conv-relu_conv1节点的输出实际为Convolution后Relu的输出,其中 Relu 节点在模型转换时被融合进Convolution节点,此处不影响计算;

此输出 tensor 对应下一个 Node 的输入。

不同算子的Attributes

目前提供92个 Node 类型(即算子类型,但包括了对 INPUT 和 Const 的处理)的解析。

算子列表

其中无参数算子如下表:

编号 算子 分类
0 Accuracy /
4 Const /
8 DropOut Dropout
12 INPUT INPUT
17 Prelu Activation
21 ReLU6 Activation
29 Split Shape
33 Logistic Activation
36 TanH Activation
37 Sigmoid Activation
39 FusedbnScaleRelu Activation
46 Max Layer
47 Min Layer
62 Noop Layer
68 Absval Data
74 BroadMul Layer
81 Reverse Shape
83 Ceil Layer
84 SquaredDifference Layer
85 Round Layer
86 ZerosLike Layer
90 Mean Layer
91 MatMul Layer
94 Shape Shape
95 Where /
97 Mish Activation
98 Num Layer

(表中“分类”一栏对算子进行了分类,与其显示颜色有关,“/”代表未知分类。)

有参数算子如下表:

编号 算子 分类
1 BatchNormalization Normalization
2 BilinearResize Shape
3 Concat Shape
5 Convolution Layer
6 DeConvolution Layer
7 DetectionOutput Layer
9 Eltwise /
10 Flatten Shape
11 FullyConnected Layer
13 LRN Normalization
14 Normalize Normalization
15 Permute Shape
16 Pooling Pool
18 PriorBox /
19 Region /
20 ReLU Activation
22 Reorg Shape
23 Reshape Shape
24 RoiPooling Pool
25 RPN /
26 Scale Layer
27 Slice Shape
28 SoftMax Activation
30 DetectionPostProcess Layer
31 Gemm /
32 Generic /
34 LSTM Layer
35 RNN Layer
38 Squeeze Shape
40 Pad Layer
41 StridedSlice Shape
42 ArgMax Layer
43 ArgMin Layer
44 TopKV2 Layer
45 Reduction /
48 GRU Layer
49 Addn /
50 SwapAxis Shape
51 Upsample Data
52 SpaceToBatchND Shape
53 BatchToSpaceND Shape
54 Resize Data
55 ShuffleChannel Shape
56 Crop Shape
57 ROIAlign /
58 Psroipooling Pool
59 Unary /
60 Expanddims Shape
61 Bias Layer
63 Threshold Activation
64 Hardsigmoid Activation
65 Embed Transform
66 InstanceNorm Normalization
67 MVN /
69 Cast /
70 HardSwish Activation
71 Interp Layer
72 SELU Activation
73 ELU Activation
75 Logical Layer
76 Gather Data
77 Transpose Transform
78 Comparison Layer
79 SpaceToDepth Shape
80 DepthToSpace Shape
82 SparseToDense Shape
87 Clip Layer
88 Unsqueeze Transform
89 ReduceL2 Layer
92 Expand Layer
93 Scatter Layer
96 Tile Layer

(表中“分类”一栏对算子进行了分类,与其显示颜色有关,“/”代表未知分类。)

有参数算子属性列表

BatchNormalization
参数 数据类型 说明
rescale_factor float32 默认值 1
eps float32 默认值 1e-5
caffe_flavor int32 默认值 0
BilinearResize
参数 数据类型 说明
scale_x float32 水平方向变换因子
scale_y float32 垂直方向变换因子
type int32 0: NEAREST_NEIGHBOR 1: BILIEAR
Concat
参数 数据类型 说明
axis int32 合并操作轴,支持“0,1,2,3”,NCHW 默认为1, NHWC 默认为3.
Convolution
参数 数据类型 说明
kernel_h int32 垂直方向 Kernel 大小,默认值为1
kernel_w int32 水平方向 Kernel 大小,默认值为1
stride_h int32 垂直方向 Stride 大小,默认值为1
stride_w int32 水平方向 Stride 大小,默认值为1
dilation_h int32 垂直方向空洞因子值,默认值为1
dilation_w int32 水平方向空洞因子值, 默认值为1
input_channel int32 输入特征图通道数(creat_graph后)
output_channel int32 输出特征图通道数
group int32 分组数,默认值为 1
activation int32 是否和Relu合并,0:RELU 1: RELU1 6: RELU6,默认值为-1
pad_h0 int32 top padding rows,默认值为0
pad_w0 int32 left padding columns,默认值为0
pad_h1 int32 bottom padding rows,默认值为0
pad_w1 int32 right padding columns,默认值为0
DeConvolution
参数 数据类型 说明
num_output int32 输出元素个数
kernel_h int32 垂直方向 Kernel 大小
kernel_w int32 水平方向 Kernel 大小
stride_h int32 垂直方向 Stride 大小
stride_w int32 水平方向 Stride 大小
pad_w0 int32 left padding columns
pad_h0 int32 top padding rows
pad_w1 int32 right padding columns
pad_h1 int32 bottom padding rows
dilation_h int32 垂直方向空洞因子值
dilation_w int32 水平方向空洞因子值
group int32 分组数,默认值为 1
activation int32 是否和Relu合并,0:RELU 1: RELU1 6: RELU6
DetectionOutput
参数 数据类型 说明
num_classes int32 检测类别数
keep_top_k int32 NMS操作后, bounding box 个数
nms_top_k int32 NMS操作前,置信度高的预测框的个数
confidence_threshold float32 置信度阈值
nms_threshold float32 非极大值抑制阈值
Eltwise
参数 数据类型 说明
type uint32 0: ELT_PROD 1: ELT_PROD_SCALAR 2: ELT_SUM 3: ELT_SUM_SCALAR 4: ELT_SUB 5: ELT_SUB_SCALAR 6: ELT_MAX 7: ELT_RSQRT 8: ELT_DIV 9: ELT_LOG 10: ELT_EXP 11: ELT_SQRT 12: ELT_FLOOR 13: ELT_SQUARE 14: ELT_POW 15: ELT_POWER
caffe_flavor int32 是否支持caffe 格式 1:表示caffe 框架计算模式
Flatten
参数 数据类型 说明
axis int32 起始轴
end_axis int32 终止轴
FullyConnected
参数 数据类型 说明
num_output int32 输出特征图大小
LRN
参数 数据类型 说明
local_size int32 归一化区域大小
alpha float32 默认为1e-05
beta float32 默认为0.75
norm_region int32 Norm 范围
k float32 默认为2
Normalize
参数 数据类型 说明
across_spatial int32 表示是否对整个图片进行归一化
channel_shared int32 表示 scale 是否相同
Permute
参数 数据类型 说明
flag int32 未使用
order0 int32 permute 之前的轴
order1 int32 permute 之前的轴
order2 int32 permute 之前的轴
order3 int32 permute 之前的轴
Pooling
参数 数据类型 说明
alg int32 说明 pooling的计算方法,0 :MaxPooling 1:AvgPooling
kernel_h int32 垂直方向 Kernel 大小
kernel_w int32 水平方向 Kernel 大小
stride_h int32 垂直方向 Stride 大小
stride_w int32 水平方向 Stride 大小
global int32 1:Global Pooling 标志
caffe_flavor int32 1:Caffe 框架特殊处理标志
pad_h0 int32 top padding columns
pad_w0 int32 left padding rows
pad_h1 int32 bottom padding columns
pad_w1 int32 right padding rows
PriorBox
参数 数据类型 说明
offset_vf_min_size tm_uoffset_t offset of TM2_Vector_floats
offset_vf_max_size tm_uoffset_t offset of TM2_Vector_floats
offset_vf_variance tm_uoffset_t offset of TM2_Vector_floats
offset_vf_aspect_ratio tm_uoffset_t offset of TM2_Vector_floats
flip int32 是否翻转,默认值为 0
clip int32 是否裁剪,默认值为 0
img_size int32 候选框大小
img_h int32 候选框在 height 上的偏移
img_w int32 候选框在 width 上的偏移
step_w float32 候选框在 width 上的步长
step_h float32 候选框在 height 上的步长
offset float32 候选框中心位移
num_priors int32 默认候选框个数
out_dim int32 输出个数
Region
参数 数据类型 说明
num_classes int32 检测类别总数
side int32 NULL
num_box int32 候选框数
coords int32 坐标个数
confidence_threshold float32 置信度阈值
nms_threshold float32 非极大值抑制阈值
offset_vf_biases tm_uoffset_t offset of TM2_Vector_floats
ReLU
参数 数据类型 说明
negative_slope float32 对标准的ReLU函数进行变化,默认值为0
Reorg
参数 数据类型 说明
Stride int32 步进大小
Reshape
参数 数据类型 说明
dim_0 int32 Batch
dim_1 int32 Channel
dim_2 int32 Height
dim_3 int32 Width
dim_size int32 Dim 大小
axis int32 指定 reshape 维度
RoiPooling
参数 数据类型 说明
pooled_h int32 池化高度
pooled_w int32 池化宽度
spatial_scale float32 用于将 cords 从输入比例转换为池化时使用的比例
RPN
参数 数据类型 说明
offset_vf_ratios tm_uoffset_t pointer to TM2_Vector_floats
offset_vf_anchor_scales tm_uoffset_t pointer to TM2_Vector_floats
feat_stride int32 特征值步进大小
basesize int32 基础尺寸
min_size int32 最小尺寸
per_nms_topn int32 NMS操作后, bounding box 个数
post_nms_topn int32 NMS操作前,置信度高的预测框的个数
nms_thresh float32 非极大值抑制阈值
offset_va_anchors tm_uoffset_t offset of TM2_Vector_anchors
Scale
参数 数据类型 说明
axis int32 操作轴
num_axes int32 缩放的比例
bias_term int32 缩放的偏置
Slice
参数 数据类型 说明
axis int32 操作轴
offset_vi_slice_points tm_uoffset_t offset of TM2_Vector_dims 各个轴的起始维度,大小等于轴数
offset_vi_begins tm_uoffset_t offset of TM2_Vector_dims
offset_vi_sizes tm_uoffset_t offset of TM2_Vector_dims 各个轴的截止维度, 大小等于轴数
iscaffe int32 True: 表明是 caffe 框架中的 slice
ismxnet int32 True: 表明是 mxnet 框架中的slice
begin int32 各个轴上切片的起始索引值
end int32 各个轴上切片的结束索引值
SoftMax
参数 数据类型 说明
axis int32 操作轴
DetectionPostProcess
参数 数据类型 说明
max_detections int32 最大检测数量
max_classes_per_detection int32 每个检测框中的最大分类类别数
nms_score_threshold float32 非极大值抑制得分阈值
nms_iou_threshold float32 非极大值抑制IOU阈值
num_classes int32 检测类别总数
offset_vf_scales tm_uoffset_t Scale参数
Gemm
参数 数据类型 说明
alpha float32 生成矩阵A
beta float32 生成矩阵B
transA int32 矩阵A是否转置变换
transB int32 矩阵B是否转置变换
Generic
参数 数据类型 说明
max_input_num int32 最大输入 Tensor 个数
max_output_num int32 最小输入 Tensor 个数
offset_s_opname tm_uoffset_t Operator Name 索引
LSTM
参数 数据类型 说明
forget_bias float32 未使用
clip float32 未使用
output_len int32 输出长度
sequence_len int32 序列长度
input_size int32 输入大小
hidden_size int32 隐藏层大小
cell_size int32 单元大小
has_peephole int32 是否支持 peephole
has_projection int32 是否支持 projection
has_clip int32 是否支持 clip
has_bias int32 是否支持 bias
has_init_state int32 是否支持 init_state
forget_act int32 未使用
input_act int32 未使用
output_act int32 未使用
cellin_act int32 未使用
cellout_act int32 未使用
mxnet_flag int32 未使用
RNN
参数 数据类型 说明
clip float32 裁剪值
output_len int32 输出长度
sequence_len int32 序列长度
input_size int32 输入大小
hidden_size int32 隐藏层大小
has_clip int32 是否支持 clip
has_bias int32 是否支持 bias
has_init_state int32 是否支持 init state
activation int32 激活层类别
Squeeze
参数 数据类型 说明
dim_0 int32 Batch
dim_1 int32 Channel
dim_2 int32 Height
dim_3 int32 Width
Pad
参数 数据类型 说明
pad_n_0 int32 未使用,默认为0
pad_n_1 int32 未使用,默认为0
pad_c_0 int32 未使用,默认为0
pad_c_1 int32 未使用,默认为0
pad_h_0 int32 top padding rows
pad_h_1 int32 bottom padding rows
pad_w_0 int32 left padding columns
pad_w_1 int32 right padding columns
mode int32 0: CONSTANT 1: REFLECT 2: SYMMETRIC 3. EDGE
value float32 当 mode 为CONSTANT时,设置的常量值
StridedSlice
参数 数据类型 说明
begine_n int32 Batch 起始索引
end_n int32 Batch 结束索引
stride_n int32 Batch Slice 步进
begine_c int32 Channel 起始索引
end_c int32 Channel 结束索引
stride_c int32 Channel Slice 步进
begine_h int32 Height 起始索引
end_h int32 Height 结束索引
stride_h int32 Height Slice 步进
begine_w int32 Width 起始索引
end_w int32 Width 结束索引
stride_w int32 Width Slice 步进
ArgMax
参数 数据类型 说明
axis int32 操作轴,默认值为0
ArgMin
参数 数据类型 说明
axis int32 操作轴,默认值为0
TopKV2
参数 数据类型 说明
k int32 top 的个数
Sorted int32 true: 降序排列 false: 升序排序
Reduction
参数 数据类型 说明
dim_0 int32 Batch
dim_1 int32 Channel
dim_2 int32 Height
dim_3 int32 Width
type int32 类别
keepdim int32 指定 dim 不变
GRU
参数 数据类型 说明
clip float32 Clip 值
output_len int32 输出长度
sequence_len int32 序列长度
input_size int32 输入大小
hidden_size int32 隐藏层大小
has_clip int32 是否支持 clip
has_gate_bias int32 是否支持 gate_bias
has_candidate_bias int32 是否支持 candidate_bias
has_init_state int32 是否支持 init_state
mxnet_flag int32 未使用
Addn
参数 数据类型 说明
axis int32 操作轴,默认值为0
SwapAxis
参数 数据类型 说明
dim_0 int32 待交换的轴0
dim_1 int32 待交换的轴1
Upsample
参数 数据类型 说明
scale int32 采样因子
SpaceToBatchND
参数 数据类型 说明
dilation_x int32 Width 膨胀值
dilation_y int32 Height 膨胀值
pad_top int32 top padding rows
pad_bottom int32 bottom padding rows
pad_left int32 left padding columns
pad_right int32 right padding columns
BatchToSpaceND
参数 数据类型 说明
dilation_x int32 Width 膨胀值
dilation_y int32 Height 膨胀值
crop_top int32 top crop rows
crop_bottom int32 bottom crop rows
crop_left int32 left crop columns
crop_right int32 right crop columns
Resize
参数 数据类型 说明
scale_x float32 水平方向变换因子
scale_y float32 垂直方向变换因子
type int32 0: NEAREST_NEIGHBOR 1: BILIEAR
ShuffleChannel
参数 数据类型 说明
group int32 group 值
Crop
参数 数据类型 说明
num_args int32 参数数目
offset_c int32 C 维度方向offset
offset_h int32 垂直方向上方offset
offset_w int32 垂直方向左方offset
crop_h int32 输出垂直方向大小
crop_w int32 输出水平方向大小
center_crop bool True: 中心crop False: 按照offset crop,默认为false
axis int32 操作轴,默认值为1,用于Caffe 框架
flag int32 未使用
ROIAlign
参数 数据类型 说明
pooled_width int32 池化后的输出宽度
pooled_height int32 池化后的输出高度
spatial_scale int32 乘法性质空间标尺因子
Psroipooling
参数 数据类型 说明
pooled_w int32 池化后的输出宽度
pooled_h int32 池化后的输出高度
spatial_scale float32 乘法性质空间标尺因子
output_dim int32 输出 dims 大小
Unary
参数 数据类型 说明
type int32 0: UNARY_ABS 1: UNARY_NEG 2: UNARY_FLOOR 3: UNARY_CEIL 4: UNARY_SQUARE 5: UNARY_SQRT 6: UNARY_RSQRT 7: UNARY_EXP 8: UNARY_LOG 9: UNARY_SIN 10: UNARY_COS 11: UNARY_TAN 12: UNARY_ASIN 13: UNARY_ACOS 14: UNARY_ATAN 15: UNARY_RECIPROCAL 16: UNARY_TANH
Expanddims
参数 数据类型 说明
axis int32 操作轴
Bias
参数 数据类型 说明
bias_size int32 Bias 参数个数
Threshold
参数 数据类型 说明
Threshold float32 阈值
Hardsigmoid
参数 数据类型 说明
alpha float32 alpha 因子
beta float32 偏移参数
Embed
参数 数据类型 说明
num_output int32 输出元素个数
input_dim int32 输入数据长度
bias_term int32 1 : 表示有bias
weight_data_size int32 Weight 数据长度 必须小于等于input_dim
InstanceNorm
参数 数据类型 说明
eps float32 Eps 值
MVN
参数 数据类型 说明
across_channels int32 1:跨channel
normalize_variance int32 0:求和方式 1:求方差方式
eps float32 normalize_variance = 1,用到的因子
Cast
参数 数据类型 说明
type_from int32 0为int32 1: float32 2: float16 3:int8 4: uint8
type_to int32 0为int32 1: float32 2: float16 3:int8 4: uint8
HardSwish
参数 数据类型 说明
alpha float32 乘法因子 默认为1
beta float32 移位参数,默认为3
Interp
参数 数据类型 说明
resize_type int32 类型,未使用
width_scale float32 Width 缩放因子
height_scale float32 Height 缩放因子
output_width int32 输出 Width 大小
output_height int32 输出 Height 大小
SELU
参数 数据类型 说明
alpha float32 SeLU 激活函数中的 α 的值
lambda float32 表示SeLU激活函数中的 λ 的值
ELU
参数 数据类型 说明
alpha float32 alpha 因子,默认为1
Logical
参数 数据类型 说明
type int32 逻辑处理类型
Gather
参数 数据类型 说明
axis int32 操作轴
indices_num int32 Index 的个数
Transpose
参数 数据类型 说明
dim0 int32 Transpose 之前的轴
dim1 int32 Transpose 之前的轴
dim2 int32 Transpose 之前的轴
dim3 int32 Transpose 之前的轴
Comparison
参数 数据类型 说明
type int32 比较操作类型
SpaceToDepth
参数 数据类型 说明
block_size int32 水平方向&&垂直方向移动到 C 方向的倍数
DepthToSpace
参数 数据类型 说明
block_size int32 C 方向移动到水平方向&&垂直方向的倍数
SparseToDense
参数 数据类型 说明
output_shape_size0 int32 输出 Height 大小
output_shape_size1 int32 输出 Width 大小
default_value int32 默认 Value
Clip
参数 数据类型 说明
max float 截断操作最大值
min float 截断操作最小值
Unsqueeze
参数 数据类型 说明
offset_vi_axises tm_uoffset_t 操作轴偏移量数组
ReduceL2
参数 数据类型 说明
axis int32 操作轴
keepdim int32 保留的维度大小
Expand
参数 数据类型 说明
offset_v_shape tm_uoffset_t 输出维度数组
Scatter
参数 数据类型 说明
axis int32 操作轴
is_onnx tm_bool_t 是否为ONNX算子
Tile
参数 数据类型 说明
offset_vi_flag tm_uoffset_t caffe: 0, onnx: 1
offset_vi_reps tm_uoffset_t 用于 tile 补齐操作的数据

调试方法

计算图 Profiler

计算图 Profiler,显示完成 infer shape 操作后的已序列化 ir_graph 信息,用于确认 infer shape 是否正确。

使用方法

  • 程序执行前,添加环境变量 export TG_DEBUG_GRAPH=1,启用计算图 Profiler 功能;

  • 删除环境变量 unset TG_DEBUG_GRAPH, 关闭计算图 Profiler 功能。

性能 Profiler

性能 Profiler,用于逐层耗时统计,在网络模型运行时统计 CPU 上 kernel 耗时信息,用于分析潜在的耗时问题。

使用方法

  • 程序执行前,添加环境变量 export TG_DEBUG_TIME=1,启用性能 Profiler 功能;

  • 删除环境变量 unset TG_DEBUG_TIME, 关闭性能 Profiler 功能。

logo 信息

$./tm_classification -m mobilenet.tmfile -i cat.jpg  -r 10
tengine-lite library version: 1.4-dev
model file : mobilenet.tmfile
image file : cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 10 times, thread 1, avg time 42.36 ms, max_time 65.59 ms, min_time 38.26 ms
--------------------------------------
8.574144, 282
7.880117, 277
7.812573, 278
7.286458, 263
6.357486, 281
--------------------------------------
   0 [ 3.42% :    1.2 ms]   Convolution idx:    5 shape: {1   3 224 224} -> {1  32 112 112}       fp32 ->  fp32 K: 3x3 | S: 2x2 | P: 1 1 1 1         MFLOPS: 21.68 Rate:17722
   1 [ 4.60% :    1.6 ms]   Convolution idx:    8 shape: {1  32 112 112} -> {1  32 112 112}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW( 32) MFLOPS:  7.23 Rate:4392
   2 [ 5.66% :    2.0 ms]   Convolution idx:   11 shape: {1  32 112 112} -> {1  64 112 112}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS: 51.38 Rate:25423
   3 [ 3.28% :    1.2 ms]   Convolution idx:   14 shape: {1  64 112 112} -> {1  64  56  56}       fp32 ->  fp32 K: 3x3 | S: 2x2 | P: 1 1 1 1 DW( 64) MFLOPS:  3.61 Rate:3085
   4 [ 4.25% :    1.5 ms]   Convolution idx:   17 shape: {1  64  56  56} -> {1 128  56  56}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS: 51.38 Rate:33824
   5 [ 3.13% :    1.1 ms]   Convolution idx:   20 shape: {1 128  56  56} -> {1 128  56  56}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(128) MFLOPS:  7.23 Rate:6458
   6 [ 7.85% :    2.8 ms]   Convolution idx:   23 shape: {1 128  56  56} -> {1 128  56  56}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:36658
   7 [ 1.72% :    0.6 ms]   Convolution idx:   26 shape: {1 128  56  56} -> {1 128  28  28}       fp32 ->  fp32 K: 3x3 | S: 2x2 | P: 1 1 1 1 DW(128) MFLOPS:  1.81 Rate:2937
   8 [ 3.37% :    1.2 ms]   Convolution idx:   29 shape: {1 128  28  28} -> {1 256  28  28}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS: 51.38 Rate:42671
   9 [ 1.32% :    0.5 ms]   Convolution idx:   32 shape: {1 256  28  28} -> {1 256  28  28}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(256) MFLOPS:  3.61 Rate:7655
  10 [ 6.45% :    2.3 ms]   Convolution idx:   35 shape: {1 256  28  28} -> {1 256  28  28}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:44564
  11 [ 0.78% :    0.3 ms]   Convolution idx:   38 shape: {1 256  28  28} -> {1 256  14  14}       fp32 ->  fp32 K: 3x3 | S: 2x2 | P: 1 1 1 1 DW(256) MFLOPS:  0.90 Rate:3259
  12 [ 3.27% :    1.2 ms]   Convolution idx:   41 shape: {1 256  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS: 51.38 Rate:43954
  13 [ 1.01% :    0.4 ms]   Convolution idx:   44 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(512) MFLOPS:  1.81 Rate:4989
  14 [ 6.57% :    2.3 ms]   Convolution idx:   47 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:43767
  15 [ 0.96% :    0.3 ms]   Convolution idx:   50 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(512) MFLOPS:  1.81 Rate:5266
  16 [ 6.44% :    2.3 ms]   Convolution idx:   53 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:44659
  17 [ 1.01% :    0.4 ms]   Convolution idx:   56 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(512) MFLOPS:  1.81 Rate:5003
  18 [ 6.44% :    2.3 ms]   Convolution idx:   59 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:44678
  19 [ 0.98% :    0.3 ms]   Convolution idx:   62 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(512) MFLOPS:  1.81 Rate:5174
  20 [ 6.36% :    2.3 ms]   Convolution idx:   65 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:45249
  21 [ 1.02% :    0.4 ms]   Convolution idx:   68 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(512) MFLOPS:  1.81 Rate:4976
  22 [ 6.61% :    2.4 ms]   Convolution idx:   71 shape: {1 512  14  14} -> {1 512  14  14}       fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:43523
  23 [ 0.61% :    0.2 ms]   Convolution idx:   74 shape: {1 512  14  14} -> {1 512   7   7}       fp32 ->  fp32 K: 3x3 | S: 2x2 | P: 1 1 1 1 DW(512) MFLOPS:  0.45 Rate:2062
  24 [ 3.36% :    1.2 ms]   Convolution idx:   77 shape: {1 512   7   7} -> {1 1024   7   7}      fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS: 51.38 Rate:42853
  25 [ 0.74% :    0.3 ms]   Convolution idx:   80 shape: {1 1024   7   7} -> {1 1024   7   7}     fp32 ->  fp32 K: 3x3 | S: 1x1 | P: 1 1 1 1 DW(1024) MFLOPS:  0.90 Rate:3397
  26 [ 7.65% :    2.7 ms]   Convolution idx:   81 shape: {1 1024   7   7} -> {1 1024   7   7}     fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:102.76 Rate:37588
  27 [ 0.08% :    0.0 ms]       Pooling idx:   84 shape: {1 1024   7   7} -> {1 1024   1   1}     fp32 ->  fp32 K: 7x7 | S: 1x1 | P: 0 0 0 0         Avg
  28 [ 1.07% :    0.4 ms]   Convolution idx:   85 shape: {1 1024   1   1} -> {1 1000   1   1}     fp32 ->  fp32 K: 1x1 | S: 1x1 | P: 0 0 0 0         MFLOPS:  2.05 Rate:5360
total time: 422.97 ms. avg time: 42.30 ms. min time: 35.73 ms.

精度 Profiler

精度 Profiler,用于 CPU 后端运行网络模型后,导出每一层的 input/ouput tensor data,用于分析输出结果异常的问题。

使用方法

  • 程序执行前,添加环境变量 export TG_DEBUG_DATA=1,启用精度 Profiler 功能;

  • 删除环境变量 unset TG_DEBUG_DATA, 关闭精度 Profiler 功能。

logo 信息

数据导出后,在程序执行的当前路径下生成 ./output 文件夹。

$ ./tm_classification -m models/squeezenet.tmfile -i images/cat.jpg
model file : models/squeezenet.tmfile
image file : images/cat.jpg
label_file : (null)
img_h, img_w, scale[3], mean[3] : 227 227 , 1.000 1.000 1.000, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 4402.85 ms, max_time 4402.85 ms, min_time 4402.85 ms
--------------------------------------
0.273199, 281
0.267552, 282
0.181004, 278
0.081799, 285
0.072407, 151
--------------------------------------
$ ls ./output
conv1-conv1-bn-conv1-scale-relu1_in_blob_data.txt
conv1-conv1-bn-conv1-scale-relu1_out_blob_data.txt
conv2_1-dw-conv2_1-dw-bn-conv2_1-dw-scale-relu2_1-dw_in_blob_data.txt
conv2_1-dw-conv2_1-dw-bn-conv2_1-dw-scale-relu2_1-dw_out_blob_data.txt
conv2_1-sep-conv2_1-sep-bn-conv2_1-sep-scale-relu2_1-sep_in_blob_data.txt
conv2_1-sep-conv2_1-sep-bn-conv2_1-sep-scale-relu2_1-sep_out_blob_data.txt
conv2_2-dw-conv2_2-dw-bn-conv2_2-dw-scale-relu2_2-dw_in_blob_data.txt
conv2_2-dw-conv2_2-dw-bn-conv2_2-dw-scale-relu2_2-dw_out_blob_data.txt
conv2_2-sep-conv2_2-sep-bn-conv2_2-sep-scale-relu2_2-sep_in_blob_data.txt
conv2_2-sep-conv2_2-sep-bn-conv2_2-sep-scale-relu2_2-sep_out_blob_data.txt
conv3_1-dw-conv3_1-dw-bn-conv3_1-dw-scale-relu3_1-dw_in_blob_data.txt
conv3_1-dw-conv3_1-dw-bn-conv3_1-dw-scale-relu3_1-dw_out_blob_data.txt
conv3_1-sep-conv3_1-sep-bn-conv3_1-sep-scale-relu3_1-sep_in_blob_data.txt
conv3_1-sep-conv3_1-sep-bn-conv3_1-sep-scale-relu3_1-sep_out_blob_data.txt
conv3_2-dw-conv3_2-dw-bn-conv3_2-dw-scale-relu3_2-dw_in_blob_data.txt
conv3_2-dw-conv3_2-dw-bn-conv3_2-dw-scale-relu3_2-dw_out_blob_data.txt
conv3_2-sep-conv3_2-sep-bn-conv3_2-sep-scale-relu3_2-sep_in_blob_data.txt
conv3_2-sep-conv3_2-sep-bn-conv3_2-sep-scale-relu3_2-sep_out_blob_data.txt
conv4_1-dw-conv4_1-dw-bn-conv4_1-dw-scale-relu4_1-dw_in_blob_data.txt
conv4_1-dw-conv4_1-dw-bn-conv4_1-dw-scale-relu4_1-dw_out_blob_data.txt
conv4_1-sep-conv4_1-sep-bn-conv4_1-sep-scale-relu4_1-sep_in_blob_data.txt
conv4_1-sep-conv4_1-sep-bn-conv4_1-sep-scale-relu4_1-sep_out_blob_data.txt
conv4_2-dw-conv4_2-dw-bn-conv4_2-dw-scale-relu4_2-dw_in_blob_data.txt
conv4_2-dw-conv4_2-dw-bn-conv4_2-dw-scale-relu4_2-dw_out_blob_data.txt
conv4_2-sep-conv4_2-sep-bn-conv4_2-sep-scale-relu4_2-sep_in_blob_data.txt
conv4_2-sep-conv4_2-sep-bn-conv4_2-sep-scale-relu4_2-sep_out_blob_data.txt
conv5_1-dw-conv5_1-dw-bn-conv5_1-dw-scale-relu5_1-dw_in_blob_data.txt
conv5_1-dw-conv5_1-dw-bn-conv5_1-dw-scale-relu5_1-dw_out_blob_data.txt
conv5_1-sep-conv5_1-sep-bn-conv5_1-sep-scale-relu5_1-sep_in_blob_data.txt
conv5_1-sep-conv5_1-sep-bn-conv5_1-sep-scale-relu5_1-sep_out_blob_data.txt
conv5_2-dw-conv5_2-dw-bn-conv5_2-dw-scale-relu5_2-dw_in_blob_data.txt
conv5_2-dw-conv5_2-dw-bn-conv5_2-dw-scale-relu5_2-dw_out_blob_data.txt
conv5_2-sep-conv5_2-sep-bn-conv5_2-sep-scale-relu5_2-sep_in_blob_data.txt
conv5_2-sep-conv5_2-sep-bn-conv5_2-sep-scale-relu5_2-sep_out_blob_data.txt
conv5_3-dw-conv5_3-dw-bn-conv5_3-dw-scale-relu5_3-dw_in_blob_data.txt
conv5_3-dw-conv5_3-dw-bn-conv5_3-dw-scale-relu5_3-dw_out_blob_data.txt
conv5_3-sep-conv5_3-sep-bn-conv5_3-sep-scale-relu5_3-sep_in_blob_data.txt
conv5_3-sep-conv5_3-sep-bn-conv5_3-sep-scale-relu5_3-sep_out_blob_data.txt
conv5_4-dw-conv5_4-dw-bn-conv5_4-dw-scale-relu5_4-dw_in_blob_data.txt
conv5_4-dw-conv5_4-dw-bn-conv5_4-dw-scale-relu5_4-dw_out_blob_data.txt
conv5_4-sep-conv5_4-sep-bn-conv5_4-sep-scale-relu5_4-sep_in_blob_data.txt
conv5_4-sep-conv5_4-sep-bn-conv5_4-sep-scale-relu5_4-sep_out_blob_data.txt
conv5_5-dw-conv5_5-dw-bn-conv5_5-dw-scale-relu5_5-dw_in_blob_data.txt
conv5_5-dw-conv5_5-dw-bn-conv5_5-dw-scale-relu5_5-dw_out_blob_data.txt
conv5_5-sep-conv5_5-sep-bn-conv5_5-sep-scale-relu5_5-sep_in_blob_data.txt
conv5_5-sep-conv5_5-sep-bn-conv5_5-sep-scale-relu5_5-sep_out_blob_data.txt
conv5_6-dw-conv5_6-dw-bn-conv5_6-dw-scale-relu5_6-dw_in_blob_data.txt
conv5_6-dw-conv5_6-dw-bn-conv5_6-dw-scale-relu5_6-dw_out_blob_data.txt
conv5_6-sep-conv5_6-sep-bn-conv5_6-sep-scale-relu5_6-sep_in_blob_data.txt
conv5_6-sep-conv5_6-sep-bn-conv5_6-sep-scale-relu5_6-sep_out_blob_data.txt
conv6-dw-conv6-dw-bn-conv6-dw-scale-relu6-dw_in_blob_data.txt
conv6-dw-conv6-dw-bn-conv6-dw-scale-relu6-dw_out_blob_data.txt
conv6-sep-conv6-sep-bn-conv6-sep-scale-relu6-sep_in_blob_data.txt
conv6-sep-conv6-sep-bn-conv6-sep-scale-relu6-sep_out_blob_data.txt
fc7_in_blob_data.txt
fc7_out_blob_data.txt
pool6_in_blob_data.txt
pool6_out_blob_data.txt

Naive Profiler

Naive Profiler,用于关闭 CPU 性能算子,后端计算只使用 Naive C 实现的 reference op,用于对比分析性能算子的计算结果。

使用方法

  • 程序执行前,添加环境变量 export TG_DEBUG_REF=1,启用 Naive Profiler 功能;

  • 删除环境变量 unset TG_DEBUG_REF, 关闭精度 Naive Profiler 功能。

Linux 工程示例

Linux 工程示例用于展示 Tengine 基于 Linux 系统的各种 CPU 架构的硬件后端运行网络模型推理。

编译

参考 源码编译(Linux) 章节生成部署所需要的以下库文件:

build-linux/install/lib/
└── libtengine-lite.so

运行

模型格式

CPU 后端支持加载 Float32/Float16/Uint8/Int8 tmfile,其中 Float16/Uint8/Int8 需要通过相应的模型量化工具获取。

推理精度设置

CPU 支持 Float32/Float16/Uint8/Int8 四种精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable CPU FP32 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = 0;

Enable CPU FP16 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP16;
opt.affinity = 0;

Enable CPU Uint8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_UINT8;
opt.affinity = 0;

Enable CPU Int8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_INT8;
opt.affinity = 0;

参考 Demo

使用 C API 预测

Linux demo 大多数基于 C API 开发,调用 C API 大致分为以下几个步骤。更详细的 API 描述请参考:Tengine C API

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = affinity;

/* inital tengine */
init_tengine();

/* create graph, load tengine model xxx.tmfile */
graph_t graph = create_graph(NULL, "tengine", model_file);

/* set the shape, data buffer of input_tensor of the graph */
int img_size = img_h * img_w * 3;
int dims[] = {1, 3, img_h, img_w};    // nchw
float* input_data = ( float* )malloc(img_size * sizeof(float));

tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0);
set_tensor_shape(input_tensor, dims, 4);
set_tensor_buffer(input_tensor, input_data, img_size * 4);

/* prerun graph, set work options(num_thread, cluster, precision) */
prerun_graph_multithread(graph, opt);

/* prepare process input data, set the data mem to input tensor */
get_input_data(image_file, input_data, img_h, img_w, mean, scale);

/* run graph */
run_graph(graph, 1);

/* get the result of classification */
tensor_t output_tensor = get_graph_output_tensor(graph, 0, 0);
float* output_data = ( float* )get_tensor_buffer(output_tensor);

/* release tengine */
free(input_data);
postrun_graph(graph);
destroy_graph(graph);
release_tengine();

使用 C++ API 预测

Linux demo 同时提供 C++ API 简化开发流程,调用 C++ API 大致分为以下几个步骤。更详细的 API 描述请参考:Tengine C++ API

/* inital tengine */
init_tengine();

tengine::Net somenet;
tengine::Tensor input_tensor;
tengine::Tensor output_tensor;

/* set runtime options of Net */
somenet.opt.num_thread = num_thread;
somenet.opt.cluster = TENGINE_CLUSTER_ALL;
somenet.opt.precision = TENGINE_MODE_FP32;
somenet.opt.affinity = affinity;

/* load model */
somenet.load_model(nullptr, "tengine", model_file.c_str());

/* prepare input data */
input_tensor.create(1, 3, img_h, img_w);
get_input_data(image_file.c_str(), ( float* )input_tensor.data, img_h, img_w, mean, scale);

/* set input data */
somenet.input_tensor("data", input_tensor);

/* forward */
somenet.run();

/* get result */
somenet.extract_tensor("prob", output_tensor);

/* release tengine */
release_tengine();

执行结果

start to run register cpu allocator
tengine-lite library version: 1.0-dev

model file : ./temp/models/mobilenet.tmfile
image file : ./temp/images/cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 656.76 ms, max_time 656.76 ms, min_time 656.76 ms
--------------------------------------
8.574148, 282
7.880116, 277
7.812579, 278
7.286453, 263
6.357488, 281
--------------------------------------

Android 工程示例

Android工程示例用于展示 Tengine 基于 Android 系统的各种 CPU 架构的硬件后端运行网络模型推理。

编译

参考 源码编译(Android) 章节生成部署所需要的以下库文件:

build-android/install/lib/
└── libtengine-lite.so

运行

模型格式

CPU 后端支持加载 Float32/Float16/Uint8/Int8 tmfile,其中 Float16/Uint8/Int8 需要通过相应的模型量化工具获取。

推理精度设置

CPU 支持 Float32/Float16/Uint8/Int8 四种精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable CPU FP32 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = 0;

Enable CPU FP16 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP16;
opt.affinity = 0;

Enable CPU Uint8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_UINT8;
opt.affinity = 0;

Enable CPU Int8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_INT8;
opt.affinity = 0;

参考 Demo

使用 C API 预测

Android demo 大多数基于 C API 开发,调用 C API 大致分为以下几个步骤。更详细的 API 描述请参考:Tengine C API

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = affinity;

/* inital tengine */
init_tengine();

/* create graph, load tengine model xxx.tmfile */
graph_t graph = create_graph(NULL, "tengine", model_file);

/* set the shape, data buffer of input_tensor of the graph */
int img_size = img_h * img_w * 3;
int dims[] = {1, 3, img_h, img_w};    // nchw
float* input_data = ( float* )malloc(img_size * sizeof(float));

tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0);
set_tensor_shape(input_tensor, dims, 4);
set_tensor_buffer(input_tensor, input_data, img_size * 4);

/* prerun graph, set work options(num_thread, cluster, precision) */
prerun_graph_multithread(graph, opt);

/* prepare process input data, set the data mem to input tensor */
get_input_data(image_file, input_data, img_h, img_w, mean, scale);

/* run graph */
run_graph(graph, 1);

/* get the result of classification */
tensor_t output_tensor = get_graph_output_tensor(graph, 0, 0);
float* output_data = ( float* )get_tensor_buffer(output_tensor);

/* release tengine */
free(input_data);
postrun_graph(graph);
destroy_graph(graph);
release_tengine();

使用 C++ API 预测

Android demo 同时提供 C++ API 简化开发流程,调用 C++ API 大致分为以下几个步骤。更详细的 API 描述请参考:Tengine C++ API

/* inital tengine */
init_tengine();

tengine::Net somenet;
tengine::Tensor input_tensor;
tengine::Tensor output_tensor;

/* set runtime options of Net */
somenet.opt.num_thread = num_thread;
somenet.opt.cluster = TENGINE_CLUSTER_ALL;
somenet.opt.precision = TENGINE_MODE_FP32;
somenet.opt.affinity = affinity;

/* load model */
somenet.load_model(nullptr, "tengine", model_file.c_str());

/* prepare input data */
input_tensor.create(1, 3, img_h, img_w);
get_input_data(image_file.c_str(), ( float* )input_tensor.data, img_h, img_w, mean, scale);

/* set input data */
somenet.input_tensor("data", input_tensor);

/* forward */
somenet.run();

/* get result */
somenet.extract_tensor("prob", output_tensor);

/* release tengine */
release_tengine();

执行结果

使用adb 连接上Android 设备,以ubuntu环境为例,命令如下:

sudo apt install adb  #安装adb,使电脑可以与Android设备通信。并查看Android设备的ip。
adb connect  [安卓设备ip]
adb devices #确保可以看到设备 

adb push tm_classification   /data/local/tmp/ 
adb push cat.jpg             /data/local/tmp/
adb push mobilenet.tmfile    /data/local/tmp/
adb push libtengine-lite.so  /data/local/tmp/

adb shell 

#此时进入了Android设备的终端
cd /data/local/tmp
./tm_classification -m mobilenet.tmfile -i cat.jpg -g 224,224 -s 0.017,0.017,0.017 -w 104.007,116.669,122.679
start to run register cpu allocator
tengine-lite library version: 1.0-dev

model file : ./temp/models/mobilenet.tmfile
image file : ./temp/images/cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 656.76 ms, max_time 656.76 ms, min_time 656.76 ms
--------------------------------------
8.574148, 282
7.880116, 277
7.812579, 278
7.286453, 263
6.357488, 281
--------------------------------------

Tengine 使用 OpenCL 进行部署

How to build

Setup Tengine project ROOT_PATH

$ export ROOT_PATH={Path of tengine-lite}

Build

-DOPENCL_LIBRARY: libOpenCL.so 路径。可通过 <sudo find /usr -name "libOpenCL.so"> 命令查询

-DOPENCL_INCLUDE_DIRS:指定CL/cl.h 路径。可通过 <sudo find /usr -name "cl.h"> 命令查询

$ cd <tengine-lite-root-dir>
$ mkdir -p build-linux-opencl
$ cmake \
-DTENGINE_ENABLE_OPENCL=ON \
-DOPENCL_LIBRARY=/usr/lib/aarch64-linux-gnu/libOpenCL.so \
-DOPENCL_INCLUDE_DIRS=/usr/include ..

$ make -j4
$ make install

Tengine 使用 TIM-VX 进行部署

编译

参考 源码编译(TIM-VX) 章节,编译生成或从第三方获取部署所需要的以下库文件:

3rdparty/tim-vx/lib/
├── libArchModelSw.so
├── libCLC.so
├── libGAL.so
├── libNNArchPerf.so
├── libOpenVX.so
├── libOpenVXU.so
└── libVSC.so

build-tim-vx-arm64/install/lib/
└── libtengine-lite.so
  • 在 Khadas VIM3 上运行时,需要使用上诉动态库替代板上 /lib 目录下的已有库文件;

  • 需要使用 TIM-VX 提供的 A311D 预编译包中的 galcore.ko ( /prebuild-sdk-a311d/lib/galcore.ko)内核驱动文件进行更新。

运行

模型格式

TIM-VX 后端只支持加载 Uint8 tmfile,因此需要使用模型量化工具将 Float32 tmfile 量化成 Uint8 tmfile。

模型量化

Float32 量化成 Uint8 tmfile 具体实现步骤及相关工具获取请参考以下链接:

推理精度设置

TIM-VX 只支持 Uint8 精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable Uint8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_UINT8;
opt.affinity = 0;

后端硬件绑定

在加载模型前,需要显式指定 TIM-VX 硬件后端 context,并在调用 graph_t create_graph(context_t context, const char* model_format, const char* fname, ...) 时传入该参数。

/* create VeriSilicon TIM-VX backend */
context_t timvx_context = create_context("timvx", 1);
add_context_device(timvx_context, "TIMVX");

/* create graph, load tengine model xxx.tmfile */
create_graph(timvx_context, "tengine", model_file);

参考 Demo

源码请参考 tm_classification_timvx.c

执行结果

运行硬件为 Khadas VIM3,内置 5Tops 算力 AI 加速器。

[khadas@Khadas tengine-lite]# ./tm_classification_timvx -m squeezenet_uint8.tmfile -i cat.jpg -r 1 -s 0.017,0.017,0.017 -r 10
Tengine plugin allocator TIMVX is registered.
Image height not specified, use default 227
Image width not specified, use default  227
Mean value not specified, use default   104.0, 116.7, 122.7
tengine-lite library version: 1.2-dev
TIM-VX prerun.

model file : squeezenet_uint8.tmfile
image file : cat.jpg
img_h, img_w, scale[3], mean[3] : 227 227 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 10 times, thread 1, avg time 2.95 ms, max_time 3.42 ms, min_time 2.76 ms
--------------------------------------
34.786182, 278
33.942883, 287
33.732056, 280
32.045452, 277
30.780502, 282

支持硬件列表

芯片厂家 设备
Amlogic A311D、S905D3
NXP iMX 8M Plus
JLQ JA310
X86-64 Simulator

支持算子列表

Tengine 使用 ACL 进行部署

编译

参考 源码编译(ACL) 章节生成部署所需要的以下库文件:

3rdparty/acl/lib/
├── libarm_compute.so
├── libarm_compute_core.so
└── libarm_compute_graph.so

build-acl-arm64/install/lib/
└── libtengine-lite.so

运行

模型格式

ACL 支持直接加载 Float32 tmfile,如果工作在 Float16 推理精度模式下,Tengine 框架将在加载 Float32 tmfile 后自动在线转换为 Float16 数据进行推理。

推理精度设置

ACL 支持 Float32Float16 两种精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable GPU FP32 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = 0;

Enable GPU FP16 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP16;
opt.affinity = 0;

后端硬件绑定

在加载模型前,需要显式指定 ACL 硬件后端 context,并在调用 graph_t create_graph(context_t context, const char* model_format, const char* fname, ...) 时传入该参数。

/* create arm acl backend */
acl_context = create_context("acl", 1);
add_context_device(acl_context, "ACL");

/* create graph, load tengine model xxx.tmfile */
create_graph(acl_context, "tengine", model_file);

参考 Demo

源码请参考 tm_classification_acl.c

执行结果

[root@localhost tengine-lite]# ./tm_mssd_acl -m mssd.tmfile -i ssd_dog.jpg -t 1 -r 10
start to run register cpu allocator
start to run register acl allocator
tengine-lite library version: 1.0-dev
run into gpu by acl
Repeat 10 times, thread 2, avg time 82.32 ms, max_time 135.70 ms, min_time 74.10 ms
--------------------------------------
detect result num: 3 
dog     :99.8%
BOX:( 138 , 209 ),( 324 , 541 )
car     :99.7%
BOX:( 467 , 72 ),( 687 , 171 )
bicycle :99.6%
BOX:( 106 , 141 ),( 574 , 415 )
======================================
[DETECTED IMAGE SAVED]:
======================================

Tengine 使用 TensorRT 进行部署

编译

参考 源码编译(TensorRT) 章节。

运行

模型格式

TensorRT 支持加载 Float32 tmfile,如果工作在 Float16 推理精度模式下,Tengine 框架将在加载 Float32 tmfile 后自动在线转换为 Float16 数据进行推理。

推理精度设置

TensorRT 支持 Float32Float16Int8 三种精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable GPU FP32 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = 0;

Enable GPU FP16 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP16;
opt.affinity = 0;

Enable GPU Int8 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_INT8;
opt.affinity = 0;

后端硬件绑定

在加载模型前,需要显式指定 TensorRT 硬件后端 context,并在调用 graph_t create_graph(context_t context, const char* model_format, const char* fname, ...) 时传入该参数。

/* create NVIDIA TensorRT backend */
context_t trt_context = create_context("trt", 1);
add_context_device(trt_context, "TRT");

/* create graph, load tengine model xxx.tmfile */
create_graph(trt_context, "tengine", model_file);

参考 Demo

源码请参考 tm_classification_trt.cpp

执行结果

nvidia@xaiver:~/tengine-lite-tq/build-linux-trt$ ./tm_classification_trt -m mobilenet_v1.tmfile -i cat.jpg -g 224,224 -s 0.017,0.017,0.017 -w 104.007,116.669,122.679 -r 10
Tengine plugin allocator TRT is registered.
tengine-lite library version: 1.2-dev
Tengine: Try using inference precision TF32 failed, rollback.

model file : /home/nvidia/tengine-test/models/mobilenet_v1.tmfile
image file : /home/nvidia/tengine-test/images/cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 2.10 ms, max_time 3.10 ms, min_time 2.03 ms
--------------------------------------
8.574147, 282
7.880117, 277
7.812574, 278
7.286457, 263
6.357487, 281
--------------------------------------

Tengine 使用 CUDA 进行部署

编译

参考 源码编译(CUDA) 章节生成部署所需要的以下库文件:

待补充

运行

模型格式

CUDA 当前仅支持加载 Float32 tmfile。

推理精度设置

CUDA 支持 Float32 一种精度模型进行网络模型推理,需要在执行 prerun_graph_multithread(graph_t graph, struct options opt) 之前通过 struct options opt 显式设置推理精度。

Enable GPU FP32 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = 0;

后端硬件绑定

在加载模型前,需要显式指定 CUDA 硬件后端 context,并在调用 graph_t create_graph(context_t context, const char* model_format, const char* fname, ...) 时传入该参数。

/* create NVIDIA CUDA backend */
context_t cuda_context = create_context("cuda", 1);
add_context_device(cuda_context, "CUDA");

/* create graph, load tengine model xxx.tmfile */
create_graph(cuda_context, "tengine", model_file);

参考 Demo

源码请参考 tm_classification_tensorrt.c

执行结果

nvidia@xaiver:~/tengine-lite-tq/build-linux-cuda$ ./tm_classification_cuda -m mobilenet_v1.tmfile -i cat.jpg -g 224,224 -s 0.017,0.017,0.017 -w 104.007,116.669,122.679 -r 10
Tengine plugin allocator CUDA is registered.
tengine-lite library version: 1.2-dev

model file : /home/nvidia/tengine-test/models/mobilenet_v1.tmfile
image file : /home/nvidia/tengine-test/images/cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 10 times, thread 1, avg time 4.58 ms, max_time 5.72 ms, min_time 4.24 ms
--------------------------------------
8.574145, 282
7.880118, 277
7.812578, 278
7.286452, 263
6.357486, 281
--------------------------------------

源码编译环境准备

依赖工具安装

编译 Tengine 依赖 git, g++, cmake, make 等以下基本工具,如果没有安装,

  • Ubuntu18.04 系统命令如下:

    sudo apt-get install cmake make g++ git
    
  • Fedora28 系统命令如下:

    sudo dnf install cmake make g++ git
    

编译选项说明

基础选项

编译选项 说明 默认值
TENGINE_ENABLE_ALL_SYMBOL 编译时是否打开所有符号 ON
TENGINE_OPENMP 编译时启用 OpenMP 并行单元 ON
TENGINE_BUILD_BENCHMARK 编译性能测试模块 ON
TENGINE_BUILD_EXAMPLES 编译 Example 模块 ON
TENGINE_BUILD_TESTS 编译单元测试模块 OFF
TENGINE_COVERAGE 编译时启用代码覆盖率测试功能 OFF
TENGINE_BUILD_CPP_API 编译时启用 C++ API OFF
TENGINE_DEBUG_DATA 编译时启用调试选项,数据提取 OFF
TENGINE_DEBUG_TIME 编译时启用调试选项,单层耗时分析 OFF
TENGINE_DEBUG_MEM_STAT 编译时启用调试选项,内存状态分析 OFF
TENGINE_ARCH_ARM_82 编译时启用 ARM 架构的 armv8.2 指令 OFF

HCL 选项

编译选项 说明 默认值
TENGINE_STANDALONE_HCL 编译时单独生成 HCL 库 OFF
TENGINE_STANDALONE_HCL_AUTO_LOAD 编译时指定 HCL 库自动加载 ON

插件选项

编译选项 说明 默认值
TENGINE_ENABLE_ACL 编译 ACL 插件 OFF
TENGINE_ENABLE_VULKAN 编译 Vulkan插件 OFF
TENGINE_ENABLE_TENSORRT 编译 TensorRT 插件 OFF
TENGINE_ENABLE_CUDABACKEND 编译 CUDA 插件 OFF
TENGINE_ENABLE_OPENCL 编译 OpenCL 插件 OFF
TENGINE_ENABLE_TIM_VX 编译 TIM-VX 插件 OFF
TENGINE_ENABLE_NNIE 编译 NNIE 插件 OFF

源码编译(Linux)

本地编译

下载 Tengine 源码

下载 Tengine 源码,位于 Tengine 的分支 tengine-lite 上:

git clone -b tengine-lite https://github.com/OAID/Tengine.git  Tengine

编译 Tengine

cd Tengine
mkdir build 
cd build
cmake ..
make
make install

编译完成后 build/install/lib 目录会生成 libtengine-lite.so 文件,如下所示:

install
├── bin
│   ├── tm_benchmark
│   ├── tm_classification
│   └── tm_mobilenet_ssd
├── include
│   └── tengine_c_api.h
└── lib
    └── libtengine-lite.so

交叉编译 Arm32/64 Linux 版本

下载源码

git clone -b tengine-lite https://github.com/OAID/Tengine.git  Tengine

安装交叉编译工具链

Arm64 Linux 交叉编译工具链为:

sudo apt install g++-aarch64-linux-gnu

Arm32 Linux 交叉编译工具链为:

sudo apt install g++-arm-linux-gnueabihf

编译 Tengine

Arm64 Linux 交叉编译

cd Tengine
mkdir build 
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake ..
make
make install

Arm32 Linux 交叉编译

cd Tengine
mkdir build 
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-linux-gnueabihf.toolchain.cmake ..
make
make install

编译完成后会生成 libtengine-lite.so 文件,并且会把相关的头文件、libtengine-lite.so 文件和相关的测试程序复制到 build/install 目录中。

源码编译(Android)

安装 Android NDK

Android NDK 下载地址

准备 android toolchain 文件

(可选) 删除debug编译参数,缩小二进制体积 android-ndk issue ,android.toolchain.cmake 这个文件可以从 $ANDROID_NDK/build/cmake 找到:

# 用编辑器打开 $ANDROID_NDK/build/cmake/android.toolchain.cmake
# 删除 "-g" 这行
list(APPEND ANDROID_COMPILER_FLAGS
  -g
  -DANDROID
  ...)

下载 Tengine 源码

git clone -b tengine-lite https://github.com/OAID/Tengine.git Tengine

编译 Tengine

Arm64 Android

cd Tengine
mkdir build-android-aarch64
cd build-android-aarch64
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_ARM_NEON=ON -DANDROID_PLATFORM=android-21 ..
make -j$(nproc)
make install

Arm32 Android

cd Tengine
mkdir build-android-armv7
cd build-android-armv7
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON -DANDROID_PLATFORM=android-19 ..
make -j$(nproc)
make install

源码编译(ACL)

简介

ARM计算库(ACL)是一套计算机视觉和机器学习功能,使用SIMD技术为ARM cpu和gpu优化。 Tengine支持与ACL的OpenCL库集成,通过ARM-Mail GPU对CNN进行推理。

support check:

sudo apt install clinfo
clinfo

结果:
Number of platforms                               1
.....

Build

ACL GPU Library

下载 ACL

$ git clone https://github.com/ARM-software/ComputeLibrary.git
$ git checkout v20.05

构建 ACL

$ scons Werror=1 -j4 debug=0 asserts=1 neon=0 opencl=1 embed_kernels=1 os=linux arch=arm64-v8a

下载 Tengine

$ git clone https://github.com/OAID/Tengine.git Tengine
$ cd Tengine

创建依赖文件

$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/acl/lib
$ mkdir -p ./3rdparty/acl/include
$ cp -rf ComputeLibrary/include/*    Tengine/3rdparty/acl/include
$ cp -rf ComputeLibrary/arm_compute  Tengine/3rdparty/acl/include
$ cp -rf ComputeLibrary/support      Tengine/3rdparty/acl/include
$ cp -rf ComputeLibrary/build/libarm_compute*.so Tengine/3rdparty/acl/lib/

构建选项

$ mkdir build-acl-arm64 && cd build-acl-arm64
$ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake \
	-DTENGINE_ENABLE_ACL=ON ..
$ make -j4
$ make install

示例

依赖库

3rdparty/acl/lib/
├── libarm_compute.so
├── libarm_compute_core.so
└── libarm_compute_graph.so

build-acl-arm64/install/lib/
└── libtengine-lite.so

Set FP16 Inference mode

Enable GPU FP16 mode

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP16;
opt.affinity = 0;

结果

[root@localhost tengine-lite]# ./tm_mssd_acl -m mssd.tmfile -i ssd_dog.jpg -t 1 -r 10
start to run register cpu allocator
start to run register acl allocator
tengine-lite library version: 1.0-dev
run into gpu by acl
Repeat 10 times, thread 2, avg time 82.32 ms, max_time 135.70 ms, min_time 74.10 ms
--------------------------------------
detect result num: 3 
dog     :99.8%
BOX:( 138 , 209 ),( 324 , 541 )
car     :99.7%
BOX:( 467 , 72 ),( 687 , 171 )
bicycle :99.6%
BOX:( 106 , 141 ),( 574 , 415 )
======================================
[DETECTED IMAGE SAVED]:
======================================

支持硬件列表

芯片厂家 型号
arm-mali T-860、G31、G52

支持算子列表

源码编译(CUDA)

How to build

Build for Linux

On Ubuntu

setup nvcc enva

$ export CUDACXX=/usr/local/cuda/bin/nvcc

build

$ cd <tengine-lite-root-dir>
$ mkdir -p build-linux-cuda && cd build-linux-cuda
$ cmake -DTENGINE_ENABLE_CUDA=ON ..

$ make -j4
$ make install

源码编译(OpenCL)

How to build

Setup Tengine project ROOT_PATH

$ export ROOT_PATH={Path of tengine-lite}

Build

-DOPENCL_LIBRARY: libOpenCL.so 路径。可通过 <sudo find /usr -name "libOpenCL.so"> 命令查询

-DOPENCL_INCLUDE_DIRS:指定CL/cl.h 路径。可通过 <sudo find /usr -name "cl.h"> 命令查询

$ cd <tengine-lite-root-dir>
$ mkdir -p build-linux-opencl
$ cmake \
-DTENGINE_ENABLE_OPENCL=ON \
-DOPENCL_LIBRARY=/usr/lib/aarch64-linux-gnu/libOpenCL.so \
-DOPENCL_INCLUDE_DIRS=/usr/include ..

$ make -j4
$ make install

源码编译(TensorRT)

How to build

Build for Linux

On Ubuntu

build

$ cd <tengine-lite-root-dir>
$ mkdir -p build-linux-trt && cd build-linux-trt
$ cmake -DTENGINE_ENABLE_TENSORRT=ON \
    -DTENSORRT_INCLUDE_DIR=/usr/include/aarch64-linux-gnu \
    -DTENSORRT_LIBRARY_DIR=/usr/lib/aarch64-linux-gnu ..

$ make -j4
$ make install

源码编译(TIM-VX )

1. 简介

TIM-VXVeriSiliconOpenVX 张量接口模块(Tensor Interface Module for OpenVX,可以视作 OpenVX 的扩展支持)。 Tengine Lite 已经完成 TIM-VX 的支持和集成, 在典型的 VeriSilicon Vivante NPU 硬件设备上,比如 Khadas VIM3 (Amlogic A311D)、Khadas VIM3L 上已经可以完成 Tengine 模型的推理。 目前支持的芯片平台有:

2. 如何编译

2.1 依赖项

依赖项有三部分:

第一部分是 TIM-VX 的源码,代码仓库在下方;目前TIM-VX版本更新较快,Tengine适配的TIM-VX版本为68b5acb,下载完TIM-VX后,需要切换至该版本。 第二部分是 芯片对应板卡的 galcore.ko 的版本,对于 linux 平台,最低版本是 6.4.3.p0.286725;对于 Android 平台,最低版本是 6.4.3.279124+1。 第三部分是 TIM-VX 的依赖库,主要是直接依赖的 libCLC.so libGAL.so libOpenVX.so libOpenVXU.so libVSC.so libArchModelSw.so 等,不同的芯片最后的库文件依赖有可能是不完全相同的(比如 Android 上依赖的是 libarchmodelSw.so),要根据拿到的 SDK 进行灵活调整。

2.2 编译过程

为了方便理解全流程的过程,首先描述编译的完整过程的流程。 在编译过程中,Tengine 将会先编译 TIM-VX 的代码,然后编译 Tengine-lite 的代码,并进行链接,链接时需要找到对应的各个芯片的用户态依赖库。需要注意的是,芯片板卡内部已经集成好的 galcore.ko 可能并不和依赖 so 的版本一致,编译时成功了也会有运行时错误打印提示版本不匹配。 此外,较早发布的系统,通常集成有较早版本的库文件,此时可以考虑烧入最新的镜像或运行升级命令进行更新。

2.3 拉取代码

这里假设是直接从 github 拉取代码,并且是拉取到同一个文件夹里。

2.3.1 拉取 TIM-VX
$ git clone https://github.com/VeriSilicon/TIM-VX.git
$ cd TIM-VX
$ git checkout 68b5acb
2.3.2 拉取 Tengine-Lite
$ git clone https://github.com/OAID/Tengine.git tengine-lite
$ cd tengine-lite

2.4 选择 Tengine-Lite 集成编译 TIM-VX 方法

Tengine-Lite 支持三种 TIM-VX 的集成编译方法,具体如下:

第一种是将 TIM-VX 代码主要部分包含在 Tengine-Lite 的代码里,一并编译,最后得到单一 libtengine-lite.so,该 so 依赖 libCLC.so 等一系列 so;

第二种是不进行代码复制,但指定 CMake 选项 -DTIM_VX_SOURCE_DIR=<你的拉取位置>/TIM-VX,此时 Tengine-Lite 会查找 TIM_VX_SOURCE_DIR 指定的位置,并自动引用正确的 TIM-VX 代码文件,其他方面和第一种一致;

第三种是不进行集成编译,指定 CMake 选项 -DTENGINE_ENABLE_TIM_VX_INTEGRATION=OFF,TIM-VX 编译为单独的 libtim-vx.so,编译完成后,libtegine-lite.so 依赖 libtim-vx.so,libtim-vx.so 依赖其他的用户态驱动 libCLC.so 等一系列 so。

一般地,Tengine 推荐第一种方法以获得单一 so,并进行集成,这样可以在 Android APK 编译时减少一个依赖。这三种方法里,都需要准备 3rdparty 依赖,对于 Linux 和 Android 准备是有区别的,请注意分别进行区分。下面的几部分编译都是按照方法一进行描述的,其他方法的编译请根据方法一进行适当修改。

2.4 准备编译 x86_64 仿真环境

TIM-VX 提供了在 x86_64 宿主系统上的预编译依赖库,此部分依赖库可以在没有 NPU 的情况下,在 PC 上进行算法的开发和验证,其功能和板卡中是一致的,精度因计算路径区别略有影响但不影响验证。

2.4.1 准备代码

这部分的目的是将 TIM-VX 的 include 和 src 目录复制到 Tengine-Lite 的 source/device/tim-vx 目录下,以便于 CMake 查找文件完成编译,参考命令如下:

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/
2.4.3 准备 x86_64 3rdparty 依赖

这部分的目的是将 TIM-VX 的 x86_64 平台用户态驱动和其他头文件放入 Tengine-Lite 的 3rdparty 准备好,以便于链接阶段查找。预编译好的库文件和头文件已经在拉取下来的 TIM-VX 的 prebuilt-sdk/x86_64_linux 文件夹下,复制的参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/x86_64
$ cp -rf ../TIM-VX/prebuilt-sdk/x86_64_linux/include/* ./3rdparty/tim-vx/include/
$ cp -rf ../TIM-VX/prebuilt-sdk/x86_64_linux/lib/*     ./3rdparty/tim-vx/lib/x86_64/
2.4.4 执行编译

编译时需要打开 TIM-VX 后端的支持,参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

编译完成后,在 build 目录下的 install 文件夹里就有编译好的 libtengine-lite.so 库文件和头文件,可用于集成开发了。 需要注意的是,如果此时还想直接运行测试 example,需要手动指定一下 LD_LIBRARY_PATH 以便找到依赖的预编译好的用户态驱动。参考命令如下,需要根据实际情况调整:

$ export LD_LIBRARY_PATH=<tengine-lite-root-dir>/3rdparty/tim-vx/lib/x86_64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

2.5 准备编译 Khadas VIM3/VIM3L Linux 平台

VIM3/VIM3L 的 linux 平台是有 NPU 预置驱动的,可以通过 sudo apt list --installed 查看已经安装的版本:

khadas@Khadas:~$ sudo apt list --installed | grep aml-npu
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
aml-npu/now 6.4.3CB-2 arm64
khadas@Khadas:~$ 

对于 6.4.3CB-2 的版本(galcore 内核打印为 6.4.3.279124CB),推荐进行联网执行升级:

sudo apt-get update
sudo apt-get upgrade 
sudo apt-get full-upgrade

当前的升级版本是 6.4.4.3AAA(galcore 的内核打印是 6.4.4.3.310723AAA),升级后编译时不需要准备 3rdparty 的对应 so,系统默认的版本就可以满足要求。

下面针对这两种情况,分别会进行讨论;然而新的 npu 驱动版本支持更多的 OP,升级总是没错的(如果烧录的是较早的镜像,NPU 版本可能是 6.4.2,和 6.4.3CB-2 一样不支持 TIM-VX,视同 6.4.3CB-2 进行编译即可,或进行推荐的升级按 6.4.4 及以上版本的流程进行编译)。

2.5.1 准备代码

准备代码环节不用考虑 VIM3/VIM3L 的 NPU 版本,参考命令如下:

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/
2.5.2 准备 VIM3/VIM3L 较早版本 3rdparty 依赖

如果是较早版本的 NPU 依赖库 (6.4.3.p0.286725),不打算/不可能进行升级,那么参考准备步骤如下:

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_A311D_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_A311D_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_A311D_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-a311d
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../prebuild-sdk-a311d/include/*  ./3rdparty/tim-vx/include/
$ cp -rf ../prebuild-sdk-a311d/lib/*      ./3rdparty/tim-vx/lib/aarch64/

上面的命令是假设板子是 VIM3,对于 VIM3L,参考命令如下:

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_S905D3_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-s905d3
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../prebuild-sdk-s905d3/include/*  ./3rdparty/tim-vx/include/
$ cp -rf ../prebuild-sdk-s905d3/lib/*      ./3rdparty/tim-vx/lib/aarch64/

注意,以上步骤都是假设 PC 的操作系统是 Linux,或者准备在板卡的 Linux 系统上编译;如果宿主系统是 WSL,请务必全部流程在 WSL 的命令行里面执行,不要在 windows 下执行,避免软连接问题。

2.5.3 准备 VIM3/VIM3L 较早版本的编译

在这一步骤里,需要注意区分是要在 PC 上进行交叉编译还是在板卡上进行本地编译,本地编译比较简单,交叉编译复杂一些,但比较快。 假定前面的操作都是在板卡上进行的,那么执行的就是本地编译。本地编译时,尤其要注意,系统中的 lib 如果和 3rdparty 里面的有所不同时,那么链接时有可能链接到系统里面的版本,而不是 3rdparty 下面的版本。运行出错时需要用 ldd 命令检查一下链接情况。优先推荐先进行一下文件替换,然后再进行本地编译(如何替换请见 FAQ)。假设准备工作已经参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

编译完成后,在 build 目录下的 install 文件夹里就有编译好的 libtengine-lite.so 库文件和头文件,可用于集成开发了。 需要注意的是,如果此时还想直接运行测试 example,并且没有替换文件,需要手动指定一下 LD_LIBRARY_PATH 以便找到依赖的预编译好的用户态驱动。参考命令如下,需要根据实际情况调整:

$ export LD_LIBRARY_PATH=<tengine-lite-root-dir>/3rdparty/tim-vx/lib/x86_64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

执行后,请再用 ldd libtengine-lite.so 命令检查一下,确保 NPU 驱动指向 3rdparty 目录的 so 一系列文件(替换是优先的选择)。

如果是交叉编译,那么请注意检查交叉编译的编译器不要高于板卡上的 gcc 版本,否则会导致在班子运行时,符号找不到的问题。确认这一点后,就可以进行交叉编译了。 如果手头没有合适的交叉编译工具,或者系统安装的版本较高,可以使用如下命令进行下载 linaro aarch64 工具链(或 arm release 的版本,二选一即可):

$ wget -c http://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz
$ tar xf gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz

下载完成后,进行交叉编译,参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=aarch64 -DCMAKE_C_COMPILER=`pwd`/../../gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=`pwd`/../../gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++ ..
$ make -j`nproc` && make install

如果系统安装的 gcc-aarch64-linux-gnu/g++-aarch64-linux-gnu 满足板子的 gcc 版本要求,也可以通过如下参考命令进行编译:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake ..
$ make -j`nproc` && make install
2.5.4 准备 VIM3/VIM3L 最新版本 3rdparty 依赖

最佳实践是升级系统到最新版本,使得 NPU 的版本 >= 6.4.4。此时没有预置的 3rdparty,所以优先推荐的是采用在板上编译的方式进行编译,这时由于必要的 TIM-VX 依赖用户态驱动的 so 都已在系统目录下,不用准备 3rdparty 下的 lib 目录。参考命令如下:

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_S905D3_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-s905d3
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../prebuild-sdk-s905d3/include/*  ./3rdparty/tim-vx/include/

可以看出,只需要准备 include 文件夹到 3rdparty 即可。 如果确要进行交叉编译,此时下载到的 lib 目录下的 so 和板子是不匹配的,那么只需要按文件列表进行提取,这些文件在板卡的 /usr/lib 目录下;提取完成后,放入 ./3rdparty/tim-vx/lib/aarch64 目录下即可。文件列表可见下载到的压缩包里面的 lib 目录,或 FAQ 部分的文件列表。

2.5.5 准备 VIM3/VIM3L 最新版本的编译

在板子上进行本地编译很简单,参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

如果是交叉编译,那么请参考前面 [2.5.3 准备 VIM3/VIM3L 较早版本的编译] 部分准备交叉工具链并进行编译即可。

2.5.5.6 准备 VIM3/VIM3L 最新版本的编译(更新于2022.04.14)

VIM3近期有过一次系统升级,升级版本是 6.4.6.2.5.3.2(galcore 的内核打印是 6.4.6.2.5.3.2),该版本驱动和库存在一些问题,不能直接编译。经验证后,需要借助TIM-VX提供的驱动和npu相关库文件,下面提供一个可行的办法:

- 更新khadas vim3驱动和库

$ wget https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.37/aarch64_A311D_6.4.9.tgz
$ tar zxvf aarch64_A311D_6.4.9.tgz
$ cd vim3_aarch64/lib
$ sudo rmmod galcore
$ sudo insmod galcore.ko
$ sudo cp -r `ls -A | grep -v "galcore.ko"` /usr/lib

请反复确认驱动是否加载成功,或者多执行几次

- 下载TIM-VX仓库

$ git clone https://github.com/VeriSilicon/TIM-VX.git
$ cd TIM-VX && git checkout v1.1.37

- 下载Tengine仓库并编译

$ git clone https://github.com/OAID/Tengine
$ cd Tengine && git checkout 8c9a85a
$ cp -rf ../TIM-VX/include ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src ./source/device/tim-vx/
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../vim3_aarch64/include/*  ./3rdparty/tim-vx/include/
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

2.6 编译 EAIS-750E Linux平台

EAIS-750E是OPEN AI LAB官方推出的工业智能盒子,主控使用Amlogic A311D芯片,也是Tengine的参考开发平台。系统Linux系统包含 NPU 驱动(6.4.4.3AAA)和Tengine-Lite v1.5库( /usr/lib 目录下),可以直接调用。如果为了学习调试Tengine或者为了升级Github最新版本,可以手动在盒子上本地编译。

- 下载TIM-VX和Tengine代码仓库

$ git clone https://github.com/VeriSilicon/TIM-VX.git
$ git clone https://github.com/OAID/Tengine.git
$ cd Tengine
$ cp -rf ../TIM-VX/include ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src ./source/device/tim-vx/

- 准备编译环境 编译依赖的系统库在EAIS-750E板载/usr/lib/下能搜索到,因此只需要拷贝头文件

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_A311D_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_A311D_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_A311D_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-a311d
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../prebuild-sdk-a311d/include/*  ./3rdparty/tim-vx/include/

- 如遇到cmake版本不够,更新板子上的cmake版本

$ sudo apt-get autoremove cmake
$ wget https://cmake.org/files/v3.22/cmake-3.22.0-linux-aarch64.tar.gz
$ tar -xzvf cmake-3.22.0-linux-aarch64.tar.gz
$ sudo mv cmake-3.22.0-linux-aarch64 /opt/cmake-3.22
$ ln -sf /opt/cmake-3.22/bin/*  /usr/bin/
$ cmake --version

- 编译Tengine

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

编译完成后,将build/install/lib/libtengine-lite.so 文件拷贝到/usr/lib/下替换原有库完成安装。

2.7 编译 NXP i.MX 8M Plus linux 平台

以 i.MX 8M Plus 的官方开发板 8MPLUSLPD4-EVK 为例,优先推荐在板子上本地编译的方法进行编译,准备工作较为简单。

2.7.1 准备代码

和前面 VIM3/VIM3L 的本地编译准备过程相同,参考代码如下:

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/
2.7.2 准备 3rdparty 依赖

准备过程和 VIM3/VIM3L 本地编译 NPU 最新版本相同,只需要准备 3rdparty 的 include 文件夹即可。

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_S905D3_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-s905d3
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch64
$ cp -rf ../prebuild-sdk-s905d3/include/*  ./3rdparty/tim-vx/include/
2.7.3 编译
$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON ..
$ make -j`nproc` && make install

完成编译后,即可考虑测试一下 example 的内容,或进行其他相关开发工作了。

2.8 编译 Rockchip RV1109/RV1126 buildroot 平台

瑞芯微的 RV1109/RV1126 芯片只有 buildroot,没有完整系统的概念,所以不能进行本地编译,只能交叉编译。 解压缩 RockChip 提供(或板卡厂商代为提供)的 RV1109/RV1126 SDK 后,在 prebuilt 目录里面可以找到交叉编译的工具链 gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf (另一套 linaro 的不是用来编译应用的,忽略)。 在 SDK 的 external/rknpu/drivers/linux-armhf-puma/usr/lib 目录下的文件,就是我们需要的 NPU 编译依赖库。

2.8.1 准备代码

和前面 VIM3/VIM3L 的本地编译准备过程相同,参考命令如下:

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/
2.8.2 准备3rdparty 依赖

准备的 include 目录和 VIM3/VIM3L 本地编译 NPU 最新版本相同,下载一份 perbuild SDK,将其中的 include 文件夹复制到 3rdparty/tim-vx 目录。 依赖的 lib 目录下的文件需要从前面 SDK 中解压出来的 external/rknpu/drivers/linux-armhf-puma/usr/lib 目录提取。将该目录下的文件全部(实际上不需要全部复制,FAQ 有文件列表)复制到 3rdparty/tim-vx/lib/aarch32 文件夹下即可。 完整过程的参考命令如下:

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ tar zxvf aarch64_S905D3_D312513_A294074_R311680_T312233_O312045.tgz
$ mv aarch64_S905D3_D312513_A294074_R311680_T312233_O312045 prebuild-sdk-s905d3
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/aarch32
$ cp -rf ../prebuild-sdk-s905d3/include/*   ./3rdparty/tim-vx/include/
$ cp -rf <rk_sdk_npu_lib>/*                 ./3rdparty/tim-vx/lib/aarch32/

注意,<rk_sdk_npu_lib> 是指 SDK 解压出来的 external/rknpu/drivers/linux-armhf-puma/usr/lib 的完整路径,需要按实际情况进行修改。

2.8.3 编译

准备交叉编译工具链,需要设置环境变量 PATH 使其能够找到工具链的 gcc/g++,参考命令如下:

export PATH=<cross_tool_chain>/bin:$PATH

需要注意,<cross_tool_chain> 是指工具链 gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf 从 SDK 解压后的实际位置,需按实际情况修改。 开发板上不一定有 OpenMP 的运行时库 libgomp.so,因此在 CMake 配置时需要给 CMake关闭 OpenMP 选项。完整编译过程参考命令如下:

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ export PATH=<cross_tool_chain>/bin:$PATH
$ ln -s ../3rdparty/tim-vx/lib/aarch32/libOpenVX.so.1.2 ../3rdparty/tim-vx/lib/aarch32/libOpenVX.so
$ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-linux-gnueabihf.toolchain.cmake  -DTENGINE_ENABLE_TIM_VX=ON -DTENGINE_OPENMP=OFF ..
$ make -j`nproc` && make install

编译完成后,提取 install 目录下的文件到板子上测试即可。需要注意的是,部分 OpenCV 依赖的 example 在这个过程中不会编译,需要先准备好交叉编译的 OpenCV,并正确设置 OpenCV_DIR 到环境变量中方可打开这部分 example 的编译。

2.9 编译 Amlogic C305X/C308X buildroot 平台

TODO:还没拿到最新的 SDK(如果您有欢迎提供我们测试,自行测试请参考 RV1109/RV1126 的编译过程)…

2.10 编译 Android 32bit 平台

目前只有 VIM3/VIM3L 和 i.MX 8M Plus 的 EVK 正式支持 Android 系统,编译时需要使用 NDK 进行编译。编译之前需要准备 3rdparty 的全部文件。 3rdparty 的结构同前面 Linux 的情况一致,但此时提取到的 so 放置的目录是 3rdparty/tim-vx/lib/android

2.10.1 准备代码

代码准备和前面典型的 Linux 准备过程相同,参考代码如下:

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/
2.10.2 准备 3rdparty

假定采用下载的预编译 Android 库,参考准备的命令如下:

$ wget -c https://github.com/VeriSilicon/TIM-VX/releases/download/v1.1.28/arm_android9_A311D_6.4.3.tgz
$ tar zxvf arm_android9_A311D_6.4.3.tgz
$ mv arm_android9_A311D_6.4.3 prebuild-sdk-android
$ cd <tengine-lite-root-dir>
$ mkdir -p ./3rdparty/tim-vx/include
$ mkdir -p ./3rdparty/tim-vx/lib/android
$ cp -rf ../prebuild-sdk-android/include/*  ./3rdparty/tim-vx/include/
$ cp -rf ../prebuild-sdk-android/lib/*      ./3rdparty/tim-vx/lib/android/

使用的 Android 系统内置的 NPU 驱动版本和相关的 so 不一定和下载到的 6.4.3 版本匹配,只需要保证不低于这个版本即可。如果确有问题,可以根据下载到的压缩包解压缩出来的 lib 目录里面的文件做列表,从板卡中用 adb pull 命令从 /vendor/lib/ 目录中提取一套出来,放入 3rdparty 的相应目录里。

2.10.3 编译
$ export ANDROID_NDK=<your-ndk-root-dir>
$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON -DANDROID_PLATFORM=android-25 ..
$ make -j`nproc` && make install

完成编译后,建议使用 ADB Shell 跑测一下 example,确保板卡环境正确。APK 能够运行还需要放行 NPU 驱动的 so,具体参见 FAQ 章节

3. 演示

3.1 依赖库

build-tim-vx-arm64/install/lib/
└── libtengine-lite.so

On the Khadas VIM3, it need to replace those libraries in the /lib/

3.2 设置 uint8 推理模式

TIM-VX Library needs the uint8 network model

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_UINT8;
opt.affinity = 0;

3.3 结果

[khadas@Khadas tengine-lite]# ./example/tm_classification_timvx -m squeezenet_uint8.tmfile -i cat.jpg -r 1 -s 0.017,0.017,0.017 -r 10
Tengine plugin allocator TIMVX is registered.
Image height not specified, use default 227
Image width not specified, use default  227
Mean value not specified, use default   104.0, 116.7, 122.7
tengine-lite library version: 1.2-dev
TIM-VX prerun.

model file : squeezenet_uint8.tmfile
image file : cat.jpg
img_h, img_w, scale[3], mean[3] : 227 227 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 10 times, thread 1, avg time 2.95 ms, max_time 3.42 ms, min_time 2.76 ms
--------------------------------------
34.786182, 278
33.942883, 287
33.732056, 280
32.045452, 277
30.780502, 282

4. uint8 量化模型

The TIM-VX NPU backend needs the uint8 tmfile as it’s input model file, you can quantize the tmfile from float32 to uint8 from here.

FAQ

Q:如何查看 NPU 驱动已经加载?A:用 lsmod 命令查看相关的驱动模块加载情况;以 VIM3 为例,检查 Galcore 内核驱动是否正确加载:

khadas@Khadas:~$ sudo lsmod
Module                  Size  Used by
iv009_isp_sensor      270336  0
iv009_isp_lens         69632  0
iv009_isp_iq          544768  0
galcore               663552  0
mali_kbase            475136  0
iv009_isp             540672  2
vpu                    49152  0
encoder                53248  0
# 中间打印略过
dhd                  1404928  0
sunrpc                446464  1
btrfs                1269760  0
xor                    20480  1 btrfs
raid6_pq              106496  1 btrfs
khadas@Khadas:~$

可以看到,galcore 663552 0 的打印说明了 galcore.ko 已经成功加载。

Q:如何查看 Galcore 的版本?A:使用 dmesg 命令打印驱动加载信息,由于信息较多,可以通过 grep 命令进行过滤。Linux 系统典型命令和打印如下:

khadas@Khadas:~$ sudo dmesg | grep Galcore
[sudo] password for khadas: 
[   17.817600] Galcore version 6.4.3.p0.286725
khadas@Khadas:~$

Android 典型命令打印如下:

kvim3:/ $ dmesg | grep Galcore
[   25.253842] <6>[   25.253842@0] Galcore version 6.4.3.279124+1
kvim3:/ $

可以看出,这个 linux 的 A311D 板卡加载的 galcore.ko 版本是 6.4.3.p0.286725,满足 linux 的版本最低要求。

Q:如何替换 galcore.ko?A:在 SDK 和内核版本升级过程中,有可能有需要升级对应的 NPU 部分的驱动,尽管推荐这一部分由板卡厂商完成,但实际上也有可能有测试或其他需求,需要直接使用最新的 NPU 版本进行测试。这时需要注意的是首先卸载 galcore.ko,然后再加载新的版本。具体命令为(假设新版本的 galcore.ko 就在当前目录):

khadas@Khadas:~$ ls
galcore.ko
khadas@Khadas:~$ sudo rmmod galcore
khadas@Khadas:~$ sudo insmod galcore.ko
khadas@Khadas:~$ sudo dmesg | grep Galcore
[   17.817600] Galcore version 6.4.3.p0.286725
khadas@Khadas:~$

这样完成的是临时替换,临时替换在下次系统启动后就会加载回系统集成的版本;想要直接替换集成的版本可以通过 sudo find /usr/lib -name galcore.ko 查找一下默认位置,一个典型的路径是 /usr/lib/modules/4.9.241/kernel/drivers/amlogic/npu/galcore.ko,将 galcore.ko 替换到这个路径即可。 替换完成后,还需要替换用户态的相关驱动文件,一般有:

libGAL.so
libNNGPUBinary.so
libOpenCL.so
libOpenVXU.so
libVSC.so
libCLC.so
libNNArchPerf.so
libNNVXCBinary.so
libOpenVX.so
libOvx12VXCBinary.so
libarchmodelSw.so

其中部分文件大小写、文件名、版本扩展名等可能不尽相同,需要保证替换前后旧版本的库及其软连接清理干净,新版本的库和软连接正确建立不疏失(有一两个 so 可能在不同的版本间是多出来或少掉的,是正常情况)。 这些文件一般在 /usr/lib/ 文件夹里面(一些板卡可能没有预置用户态的驱动和内核驱动,这时自行添加后增加启动脚本加载内核驱动即可)。

Q:替换 galcore.ko 后,怎么检查细节状态?A:有时 insmod galcore.ko 后,lsmod 时还是有 galcore 模块的,但确实没加载成功。此时可以用 dmesg 命令确认下返回值等信息,核查是否有其他错误发生。Linux 典型打印如下:

khadas@Khadas:~$ sudo dmesg | grep galcore
[    0.000000] OF: reserved mem: initialized node linux,galcore, compatible id shared-dma-pool
[   17.793965] galcore: no symbol version for module_layout
[   17.793997] galcore: loading out-of-tree module taints kernel.
[   17.817595] galcore irq number is 37.
khadas@Khadas:~$

Android 典型打印如下:

kvim3:/ $ dmesg | grep galcore
[    0.000000] <0>[    0.000000@0]      c6c00000 - c7c00000,    16384 KB, linux,galcore
[   25.253838] <4>[   25.253838@0] galcore irq number is 53.
kvim3:/ $

Q:打印提示依赖库是未识别的 ELF 格式?A:目前 3rdparty 目录下的 include 目录几乎是通用的,lib 目录和平台有关;提示这个问题有可能是解压缩或复制过程中软连接断掉了(windows 系统下常见),或者是准备的相关库文件和平台不匹配。

Q:为什么我的 Android 跑不起来对应的 APK,但 ADB Shell 跑测试程序却可以(ADB Shell 现在没放行也不可以了)?A:Android 系统不同于 Linux 系统,可以很方便的通过 GDB Server 进行远程调试,所以建议 APP 里面的集成算法部分,先在 ADB Shell 里验证一下正确性后再进行 APK 的集成。如果已经在 ADB Shell 里验证了典型的用例是正确的,APK 里面的 JNI 部分也没有其他问题,那么 APP 运行不了可以检查一下对应的 NPU 用户态驱动是否已经放行。许可文件路径是 /vendor/etc/public.libraries.txt 。许可没有放行一般提示包含有 java.lang.UnsatisfiedLinkError 错误。已经放行的 Android 许可文件大致如下图所示,libCLC.so 等已经包含进来:

kvim3:/vendor/etc $ cat public.libraries.txt
libsystemcontrol_jni.so
libtv_jni.so
libscreencontrol_jni.so
libCLC.so
libGAL.so
libOpenVX.so
libOpenVXU.so
libVSC.so
libarchmodelSw.so
libNNArchPerf.so
kvim3:/vendor/etc $

如果没有放行,需要在 ADB Shell 里面转到 root 权限,并重新挂载文件系统;重新进入 ADB Shell 后,修改完成后重启一次系统。大致操作如下:

adb root                              # 获取 root 权限
adb remount                           # 重新挂载文件系统
adb shell                             # 进入 ADB Shell
vi /vendor/etc/public.libraries.txt   # 编辑许可文件

如果对 vi 和相关命令不熟悉,可以考虑 adb pull /vendor/etc/public.libraries.txt 拉到 PC 上进行修改,然后再 adb push public.libraries.txt /vendor/etc/ 推送回板卡。

附:部分支持的板卡链接

A311D: Khadas VIM3 EAIS-750E S905D3: Khadas VIM3Li.MX 8M Plus: 8MPLUSLPD4-EVKC308X: 桐烨 C308X AI IPC

附:其他

  • 限于许可,Tengine-Lite 不能二次分发已经准备好的 3rdparty,请谅解。

  • 如果本文档描述的过程和 FAQ 没有覆盖您的问题,也欢迎加入 QQ 群 829565581 进一步咨询。

  • 不同版本的 TIM-VX 和 Tengine 对 OP 支持的情况有一定区别,请尽可能拉取最新代码进行测试评估。

  • 如果已有 OP 没有满足您的应用需求,可以分别在 TIM-VX 和 Tengine 的 issue 里创建一个新的 issue 要求支持;紧急或商业需求可以加入 QQ 群联系管理员申请商业支持。

  • Tengine 和 OPEN AI LAB 对文档涉及的板卡和芯片不做单独的保证,诸如芯片或板卡工作温度、系统定制、配置细节、价格等请与各自芯片或板卡供应商协商。

  • 如果贵司有板卡想要合作,可以加入 OPEN AI LAB 的 QQ 群联系管理员进一步沟通。

交叉编译 Arm64 OHOS(鸿蒙系统)版本

1 安装 DevEco Studio 和 OHOS NDK

下载安装 DevEco Studio,传送门。若没有华为开发者账号,需到HarmonysOS应用开发门户注册。

打开 DevEco Studio,Configure(或File)-> Settings -> Appearance & Behavior -> System Settings -> HarmonyOS SDK,勾选并下载 Native,完成 OHOS NDK 下载。

2 准备 OHOS NDK cmake toolchain 文件

ohos.toolchain.cmake 这个文件可以从 $OHOS_NDK/build/cmake 找到,例如 E:\soft\Huawei\sdk\native\3.0.0.80\build\cmake\ohos.toolchain.cmake

(可选) 删除debug编译参数,缩小二进制体积,方法和 android ndk相同 android-ndk issue

# 用编辑器打开 $ANDROID_NDK/build/cmake/android.toolchain.cmake
# 删除 "-g" 这行
list(APPEND ANDROID_COMPILER_FLAGS
  -g
  -DANDROID

3 下载 Tengine Lite 源码

git clone https://github.com/OAID/Tengine.git tengine-lite

4 编译 Tengine Lite

Arm64 OHOS 编译脚本如下(Windows)

build/ohos-arm64-v8a.bat:

@ECHO OFF
@SETLOCAL

:: Set OHOS native toolchain root
@SET OHOS_NDK=<your-ndk-root_path, such as D:/Program/DevEcoStudio/SDK/native/2.0.1.93>


:: Set ninja.exe and cmake.exe
@SET NINJA_EXE=%OHOS_NDK%/build-tools/cmake/bin/ninja.exe
@SET CMAKE_EXE=%OHOS_NDK%/build-tools/cmake/bin/cmake.exe
@SET PATH=%OHOS_NDK%/llvm/bin;%OHOS_NDK%/build-tools/cmake/bin;%PATH%

mkdir build-ohos-armeabi-v7a
pushd build-ohos-armeabi-v7a
%CMAKE_EXE% -G Ninja -DCMAKE_TOOLCHAIN_FILE="%OHOS_NDK%/build/cmake/ohos.toolchain.cmake"  -DCMAKE_MAKE_PROGRAM=%NINJA_EXE%  -DOHOS_ARCH="armeabi-v7a" -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON .. 
%CMAKE_EXE% --build . --parallel %NUMBER_OF_PROCESSORS%
%CMAKE_EXE% --build . --target install
popd

mkdir build-ohos-arm64-v8a
pushd build-ohos-arm64-v8a
%CMAKE_EXE% -G Ninja -DCMAKE_TOOLCHAIN_FILE="%OHOS_NDK%/build/cmake/ohos.toolchain.cmake"  -DCMAKE_MAKE_PROGRAM=%NINJA_EXE%  -DOHOS_ARCH="arm64-v8a" -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON .. 
%CMAKE_EXE% --build . --parallel %NUMBER_OF_PROCESSORS%
%CMAKE_EXE% --build . --target install
popd


@ENDLOCAL

源码编译(Microsoft Visual Studio)

简介

Visual Studio开发工具和服务使任何平台和语言的应用程序开发变得容易。Tengine支持在Windows上编译。

准备

CMake >= 3.13, Visual Studio >= 2015

Before the very begging, please check CMake and Visual Studio already has been installed. CMake >= 3.13, Visual Studio Version 2017 or 2019 is recommended. For CUDA or TensorRT backend user, CMake >= 3.18 is needed. CUDA or TensorRT needs to be installed or unpackaged.

构建

下载

首先从 GitHub下载 https://github.com/OAID/Tengine.git

CMD shell user

打开 “x86 Native Tools Command Prompt for VS 201x” or “x64 Native Tools Command Prompt for VS 201x”, “201x” 是你的安装版本. 假设安装的是VS2017 ,操作如下:

set PATH=X:/your/cmake/bin;%PATH%

cd /d X:/your/downloaded/Tengine
md build
cd build
cmake.exe -G "Visual Studio 15 2017 Win64" -DTENGINE_OPENMP=OFF -DTENGINE_BUILD_EXAMPLES=OFF ..
::cmake.exe -G "Visual Studio 16 2019" -A x64 -DTENGINE_OPENMP=OFF ..
cmake.exe --build . --parallel %NUMBER_OF_PROCESSORS%
cmake.exe --build . --target install

示例

TODO

源码编译(Vulkan)

简介

Vulkan是新一代图形和计算API,它提供了对现代gpu的高效、跨平台访问,这些gpu用于从pc和控制台到移动电话和嵌入式平台的各种设备。,

如何编译

Build for Linux

在 Debian, Ubuntu 上 安装 vulkan sdk:

sudo apt instal libvulkan-dev

下载 Vulkan SDK

# download vulkan sdk
$ wget https://sdk.lunarg.com/sdk/download/1.1.114.0/linux/vulkansdk-linux-x86_64-1.1.114.0.tar.gz?Human=true -O vulkansdk-linux-x86_64-1.1.114.0.tar.gz
$ tar -xf vulkansdk-linux-x86_64-1.1.114.0.tar.gz

# setup env
$ export VULKAN_SDK=`pwd`/1.1.114.0/x86_64
$ cd <tengine-lite-root-dir>
$ mkdir -p build-linux-vulkan
$ cd build-linux-vulkan
$ cmake -DTENGINE_ENABLE_VULKAN=ON ..

$ make -j4
$ make install

Build Android Library

$ cd <tengine-lite-root-dir>
$ mkdir -p build-android-aarch64-vulkan
$ cd build-android-aarch64-vulkan
$ cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI="arm64-v8a" \
    -DANDROID_PLATFORM=android-24 -DTENGINE_ENABLE_VULKAN=ON ..

$ make -j4
$ make install

示例:

violet:/data/local/tmp/tengine/vulkan $ ./tm_classification_vulkan -m mobilenet.tmfile -i cat.jpg -g 224,224 -s 0.017,0.017,0.017 -r 10
start to run register cpu allocator
start to run register vk allocator
Mean value not specified, use default   104.0, 116.7, 122.7
tengine-lite library version: 1.0-dev

model file : mobilenet.tmfile
image file : cat.jpg
img_h, img_w, scale[3], mean[3] : 224 224 , 0.017 0.017 0.017, 104.0 116.7 122.7
Repeat 10 times, thread 1, avg time 114.83 ms, max_time 169.14 ms, min_time 107.62 ms
--------------------------------------
8.574147, 282
7.880115, 277
7.812572, 278
7.286460, 263
6.357491, 281
--------------------------------------

Tengine Video Capture User Manual

约束

当前版本仅支持基于 Khadas VIM3 SBC 上的 NPU 网络模型推理演示,我们后续会逐步完善,支持基于更多硬件平台的功能演示。

默认大家手上的 Khadas VIM3 中的固件为最新版本。

硬件说明

物品 描述
Khadas VIM3 内置 A311D SoC 的单板计算机,内置 5Tops NPU 加速器
USB 摄像头 输入实时视频流
液晶显示器 控制台操作,实时输出示例运行结果
HDMI连接线 由于Khadas VIM3 的 TYPE C 接口与 HDMI 接口过于紧凑,需要寻找小一点接口的 HMDI 连接线

软件说明

以下均为 Khadas VIM3 单板计算机上的软件描述。

  • Ubuntu 20.04

  • OpenCV 4.2

  • gcc 9.3.0

  • cmake 3.16.3

操作说明

后续步骤中的命令行操作均为基于 Khadas VIM3 单板计算机上的操作,其中:

  • 下载编译步骤 可以过 SSH 登陆或者直接在 Khadas VIM3 的 Ubuntu 桌面启动控制台中执行;

  • 运行步骤仅在 Khadas VIM3 的 Ubuntu 桌面启动控制台中执行。

编译

下载 NPU 依赖库 TIM-VX

$ git clone https://github.com/VeriSilicon/TIM-VX.git

下载 Tengine

$ git clone https://github.com/OAID/Tengine.git tengine-lite
$ cd tengine-lite

准备代码

$ cd <tengine-lite-root-dir>
$ cp -rf ../TIM-VX/include  ./source/device/tim-vx/
$ cp -rf ../TIM-VX/src      ./source/device/tim-vx/

执行编译

$ cd <tengine-lite-root-dir>
$ mkdir build && cd build
$ cmake -DTENGINE_ENABLE_TIM_VX=ON -DTENGINE_ENABLE_MODEL_CACHE=ON -DTENGINE_BUILD_DEMO=ON ..
$ make demo_yolo_camera -j`nproc`

编译完成后,libtengine-lite.sodemo_yolo_camera 存放在以下路径:

  • <tengine-lite-root-dir>/build/source/libtengine-lite.so

  • <tengine-lite-root-dir>/build/demos/demo_yolo_camera

运行

模型文件 yolov3_uint8.tmfile 可从 Model ZOO 中下载,按照以下顺序方式存放文件:

......
├── demo_yolo_camera
├── libtengine-lite.so
├── models
│   └── yolov3_uint8.tmfile
......

执行当前路径下的 demo_yolo_camera

./demo_yolo_camera

P.S. :第一次运行因为会在线编译生成 NPU 运行依赖的 kernel file,会有一定的等待时间(大约30秒),后续运行直接加载所在目录下的 cache file 文件(小于1秒)。

关于容器

  • 我们提供了基于 Khadas VIM3 平台的容器版本,具体操作可以参考 deploy_superedge

  • 我们提供了腾讯云的 SuperEdge 版本,请参考(待补充)。

FAQ

Khadas VIM3 编译 Tengine + TIMVX 其余问题(包括 Khadas VIM3 购买渠道)可以参考 compile_timvx

Tengine + SuperEdge 一条指令跨平台部署边缘AI应用


案例说明

​ 案例基于开源AI推理框架Tengine 实现容器调用边缘硬件NPU资源,完成高效物体检测的推理任务,并通过开源边缘容器方案 SuperEdge 轻松将应用调度到边缘计算节点,实现一条指令部署边缘计算跨平台AI应用案例。

TengineOPEN AI LAB 主导开发,该项目实现了深度学习神经网络模型在嵌入式设备上的快速高效部署需求。为实现在众多AIoT应用中的跨平台部署,本项目使用C语言进行核心模块开发,针对嵌入式设备资源有限的特点进行了深度框架裁剪。同时采用了完全分离的前后端设计,有利于 CPU、GPU、NPU 等异构计算单元的快速移植和部署,降低评估、迁移成本。

SuperEdge 是基于原生 Kubernetes 的边缘容器管理系统。该系统把云原生能力扩展到边缘侧,很好的实现了云端对边缘端的管理和控制,极大简化了应用从云端部署到边缘端的过程。SuperEdge 为应用实现边缘原生化提供了强有力的支持。

_images/oal_structure.pngimg

硬件环境准备

物品 描述
Master服务器 SuperEdge Master 服务器, 用于应用调度,可采用X86 or Arm 架构,本例中采用X86服务器
Khadas VIM3 应用负载工作节点,内置 A311D SoC 的单板计算机,内置 5Tops NPU 加速器,各大商城有售
USB 摄像头 连接Khadas VIM3,输入实时视频流
液晶显示器 连接Khadas VIM3,控制台操作,实时输出示例运行结果
HDMI连接线 由于Khadas VIM3 的 TYPE C 接口与 HDMI 接口过于紧凑,需要寻找小一点接口的 HMDI 连接线

操作步骤

1.安装SuperEdge环境

  • 安装SuperEdge Master节点(x86_64)

wget https://superedge-1253687700.cos.ap-guangzhou.myqcloud.com/v0.4.0/amd64/edgeadm-linux-amd64-v0.4.0.tgz
tar -zxvf edgeadm-linux-amd64-v0.4.0.tgz
cd edgeadm-linux-amd64-v0.4.0
./edgeadm init --kubernetes-version=1.18.2 --image-repository superedge.tencentcloudcr.com/superedge --service-cidr=10.96.0.0/12 --pod-network-cidr=10.224.0.0/16 --install-pkg-path ./kube-linux-*.tar.gz --apiserver-cert-extra-sans=<Master Public IP> --apiserver-advertise-address=<Master Intranet IP> --enable-edge=true
#复制k8s配置文件到用户目录下
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
#去掉资源限制,解决khadas VIM3安装SuperEdge导致设备重启的问题
kubectl patch DaemonSet kube-proxy -n kube-system --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value":{}}]'
kubectl patch DaemonSet kube-flannel-ds -n kube-system --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value":{}}]'
kubectl patch DaemonSet tunnel-edge -n edge-system --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value":{}}]'
kubectl patch DaemonSet edge-health -n edge-system --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value":{}}]'
kubectl patch DaemonSet application-grid-wrapper-node -n edge-system --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value":{}}]'
  • Khadas VIM3 设备加入集群

# 由于demo使用了桌面GUI画图,开机登录界面导致应用无法正常启动,因此需设置设备开机桌面自动登录
#步骤:打开图形桌面右上角 settings-users-AutoLogin 配置,开机无需输入密码进入桌面,重新启动,无登录画面即可

#Disable fenix-zram-config service to disable the swap 
sudo systemctl disable fenix-zram-config
sudo systemctl status fenix-zram-config

# Download edgeadm arm64 version to install SuperEdge Node 
wget https://superedge-1253687700.cos.ap-guangzhou.myqcloud.com/v0.4.0/arm64/edgeadm-linux-arm64-v0.4.0.tgz
tar -zxvf edgeadm-linux-arm64-v0.4.0.tgz
cd edgeadm-linux-arm64-v0.4.0

#Upgrade cni-plugins from v0.8.3 to v0.8.6, 解决在Khadas上安装SuperEdge和CNI失败的问题,
tar -zxvf kube-linux-arm64-v1.18.2.tar.gz
wget https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-arm64-v0.8.6.tgz
mv cni-plugins-linux-arm64-v0.8.6.tgz edge-install/cni/cni-plugins-linux-arm64-v0.8.3.tgz
sed -i 's/\tload_kernel/# load_kernel/' edge-install/script/init-node.sh
tar -zcvf kube-linux-arm64-v1.18.2.1.tar.gz edge-install/

#加入集群
./edgeadm join <Master Public/Intranet IP Or Domain>:6443 --token xxxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxx --install-pkg-path kube-linux-arm64-v1.18.2.1.tar.gz --enable-edge=true
  • Khadas VIM3 设备开启Xserver授权

# Access to Xserver
# Execute script on device Terminal
xhost +

2.(可选)构建Tengine demo容器镜像


该步骤介绍如何构建Tengine Demo镜像,如采用Docker Hub镜像, 可跳过。

  • 下载文件包到Khadas VIM3设备上,构建Tengine物体识别应用docker镜像

#Download docker build packeage [~91M] from OPEN AI LAB server
wget http://tengine2.openailab.com:9527/openailab/yolo.tar.gz
tar -zxvf yolo.tar.gz
cd superedge
docker build -t yolo:latest .

Dockerfile文件如下所示

FROM ubuntu:20.04
MAINTAINER openailab
RUN apt-get update
RUN apt-get install -y tzdata
RUN apt-get install -y libopencv-dev
RUN apt-get install -y libcanberra-gtk-module
RUN useradd -m openailab
COPY libtengine-lite.so /root/myapp/
COPY demo_yolo_camera /root/myapp/
COPY tm_330_330_330_1_3.tmcache /root/myapp/
ADD models /root/myapp/models/
COPY tm_88_88_88_1_1.tmcache /root/myapp/
COPY tm_classification_timvx /root/myapp/
COPY libOpenVX.so /lib/
COPY libGAL.so /lib/
COPY libVSC.so /lib/
COPY libArchModelSw.so /lib/
COPY libNNArchPerf.so /lib/
COPY libgomp.so.1 /lib/aarch64-linux-gnu/
COPY libm.so.6 /lib/aarch64-linux-gnu/
WORKDIR /root/myapp/
USER openailab
CMD ["./demo_yolo_camera"]

如果需要自己编译并生成demo_yolo_camera程序,具体操作参考demo_videocapture user manual

3.编写 yolo.yaml 编排文件

  • 在SuperEdge Master节点上编辑k8s编排文件yolo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: yolo
  labels:
    name: yolo
spec:
  replicas: 1
  selector:
    matchLabels:
      name: yolo
  template:
    metadata:
      labels:
        name: yolo
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: In
                    values:
                      - khadas
      containers:
        - name: yolo
          image: tengine3/yolo:v1.0
          env:
          - name: DISPLAY
            value: :0
          volumeMounts:
            - name: dev
              mountPath: /dev
            - name: unix
              mountPath: /tmp/.X11-unix
          securityContext:
            privileged: true
      volumes:
        - name: dev
          hostPath:
            path: /dev
        - name: unix
          hostPath:
            path: /tmp/.X11-unix

4. Tengine物体识别应用应用部署

执行编排文件

kubectl apply -f yolo.yaml

5.案例效果与验证

通过命令查看部署状态

peter@peter-VirtualBox:~$ kubectl get deployment yolo -o wide
NAME   READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES               SELECTOR
yolo   1/1     1            1           21h   yolo         tengine3/yolo:v1.0   name=yolo

peter@peter-VirtualBox:~$ kubectl get pod yolo-76d95967bb-zxggk 
NAME                    READY   STATUS    RESTARTS   AGE
yolo-76d95967bb-zxggk   1/1     Running   3          79m

打开Khadas VIM设备的显示器,观察到如下效果

_images/oal_demo.jpgimg

Tengine Lite with Opensource DeepLearning Accelerator

1. 简介

opendla是基于英伟达开源的加速器NVDLA,之所以后端的名称叫opendla是因为英伟达官方的仓库已经停止维护两年了,而显然NVDLA还有许多可以改进的空间,改进之后的加速器需要和原来的NVDLA作区分,索性就直接叫opendla了,暂时在 ZYNQ-NVDLA 这个仓库维护。

现在的后端,只对接了 NVDLA 的 small 配置,有如下特点:

  1. ZYNQ 7045 | XCZU9EG-2 可以跑到 100 Mhz

  2. 8*8 的 PE 阵列

  3. 没有 Global SRAM 缓存

  4. 没有查找表电路

  5. 没有RUBIK数据重排引擎

  6. 目前支持的算子有:Conv|Relu|Min/Max/Avg Pooling|FullyConntected|ElementWise 其它会切给CPU运行

2. 如何编译

2.1 依赖项

依赖项有三部分:

第一部分是 芯片对应的 opendla.ko 程序,在 这篇文章 里有介绍如何编译,目前 仓库 里放置的版本是针对Linux 4.13内核的,如果是别的内核版本需要更改一些函数; 第二部分是 NVDLA 的依赖库,包括libjpeg与libprotobuf,如果是aarch64架构可以直接使用预编译好的文件。 第三部分是 NVDLA 原来支持的 Compiler 和 Runtime,需要编译出链接库放到lib目录下,如果是aarch64架构可以直接使用预编译好的文件。

2.2 编译过程

为了方便理解全流程的过程,首先描述编译的完整过程的流程。

为了编译Tengine的opendla后端支持代码,首先需要编译 libcompiler.so 与 libruntime.so,而 libcompiler 依赖 libprotobuf (版本为2.6.1),libruntime 依赖 libjpeg (版本为libjpeg6b)。

2.3 拉取代码

首先,这里演示的整个编译的过程都在开发板卡上运行,否则需要交叉编译;例子都是以root的身份来运行的;如何使用开发板连网可以参考 这篇文章

2.3.1 拉取 ZYNQ-NVDLA
$ git clone https://github.com/LeiWang1999/ZYNQ-NVDLA # clone不下来的话就本地下载用sftp传上去吧:D
2.3.2 拉取 Tengine-Lite
$ git clone https://github.com/OAID/Tengine.git Tengine

2.4 Tengine-Lite 集成编译 opendla

Tengine-Lite 目前只支持一种 opendla 的集成编译方法,即编译opendla的软件支持,首先生成.so文件,而在Tengine编译opendla后端的时候进行链接。

其他的方案,例如在Tengine编译的过程中连同opendla的编译器和运行时的源代码一起编译,由于代码肯定是要重构的,所以现在还不支持。

这里不将内核驱动程序opendla.ko是如何编译的,如何在Petalinux里编译看这篇 文章

如果是 aarch64 的架构,可以直接使用 prebuilt 的lib。

2.4.0 载入内核驱动程序
$ insmod /lib/modules/4.19.0-xilinx-v2019.1/extra/opendla.ko

使用dmesg查看内核日志:

$ dmesg | tail
[   12.817877] macb ff0e0000.ethernet eth0: link up (1000/Full)
[   12.817900] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[   20.661453] opendla: loading out-of-tree module taints kernel.
[   20.664248] Probe NVDLA config nvidia,nv_small
[   20.669152] 0.12.5
[   20.669155] reset engine done
[   20.671257] [drm] Initialized nvdla 0.0.0 20171017 for a0000000.NV_nvdla_wrapper on minor 1

查看是否注册了nvdla的中断以及nvdla驱动所需的设备renderD128是否存在来确定是否真的安装完成驱动了:

root@arm:~# insmod /lib/modules/4.19.0-xilinx-v2019.1/extra/opendla.ko 
root@arm:~# cat /proc/interrupts | grep nvdla
 45:          0          0     GIC-0  61 Level     40000000.NV_nvdla_wrapper
root@arm:~# ls /dev/dri/
card0  renderD128
2.4.1 编译libjpeg6b

如果是aarch64,跳过该步骤即可,直接使用仓库里的libjpeg.a.

$ wget http://www.ijg.org/files/jpegsrc.v6b.tar.gz
$ tar -xzvf jpegsrc.v6b.tar.gz
$ cd jpeg-6b/
$ ./configure
$ make -j `nproc`
$ make install
$ cp /usr/local/lib/libjpeg.a ~/ZYNQ-NVDLA/umd/external/ 
2.4.2 编译libprotobuf.a
$ cd ~/ZYNQ-NVDLA/umd/external/protobuf-2.6/
$ apt-get install -y autoconf automake libtool
$ autoscan & aclocal & autoconf
$ automake --add-missing
$ ./configure
$ make -j `nproc`
$ make install
$ cp /usr/local/lib/libprotobuf.a ~/ZYNQ-NVDLA/umd/apps/compiler/
$ cp /usr/local/lib/libprotobuf.a ~/ZYNQ-NVDLA/umd/core/src/compiler/
2.4.3 编译 Compiler 与 Runtime
$ cd ~/ZYNQ-NVDLA/umd/
$ make -j `nproc` TOP=${PWD} TOOLCHAIN_PREFIX=/usr/bin/ compiler
$ make -j `nproc` TOP=${PWD} TOOLCHAIN_PREFIX=/usr/bin/ runtime

这样在out目录下就会生成所需的lib,将lib和include拷贝到Tengine目录下:

$ cp ~/ZYNQ-NVDLA/include -r ~/Tengine/source/device/opendla
$ cp ~/ZYNQ-NVDLA/umd/out/core/src/compiler/libnvdla_compiler/libnvdla_compiler.so -r ~/Tengine/source/device/opendla/lib/
$ cp ~/ZYNQ-NVDLA/umd/out/core/src/runtime/libnvdla_runtime/libnvdla_runtime.so -r ~/Tengine/source/device/opendla/lib/
$ cp /usr/local/lib/libprotobuf.a ~/Tengine/source/device/opendla/lib/
2.4.4 编译 Tengine
$ cd ~/Tengine
$ mkdir build & cd build
$ cmake .. -DTENGINE_ENABLE_OPENDLA=ON

3. Demo

3.1 Classification

Resnet18-Cifar10

$ cd <tengine-lite-root-dir>/build
$ cmake --build . --target tm_classification_opendla
$ cd examples
$ ./tm_classification_opendla -m /root/Tengine/models/resnet18-cifar10-nosoftmax-relu_int8.tmfile -i /root/Tengine/images/cat.jpg -g 32,32 -s 1,1,1
Mean value not specified, use default   104.0, 116.7, 122.7
tengine-lite library version: 1.4-dev
NVDLA time: 0.012502 seconds

model file : /root/Tengine/models/resnet18-cifar10-nosoftmax-relu_int8.tmfile
image file : /root/Tengine/images/cat.jpg
img_h, img_w, scale[3], mean[3] : 32 32 , 1.000 1.000 1.000, 104.0 116.7 122.7
Repeat 1 times, thread 1, avg time 12.62 ms, max_time 12.62 ms, min_time 12.62 ms
--------------------------------------
10.087049, 3
3.833079, 2
3.026115, 5
2.420892, 4
-0.403482, 0
--------------------------------------

3.2 Detection

Yolox-nano

$ cd <tengine-lite-root-dir>/build
$ cmake --build . --target tm_classification_opendla tm_yolox_opendla
$ cd examples
$ ./tm_yolox_opendla -m /root/Tengine/models/yolox_nano_relu_int8.tmfile -i /root/Tengine/images/dog.jpg -r 1
tengine-lite library version: 1.4-dev
Repeat 1 times, thread 1, avg time 1138.80 ms, max_time 1138.80 ms, min_time 1138.80 ms
--------------------------------------
detection num: 3
 2:  70%, [ 463,   80,  676,  163], car
16:  52%, [ 122,  220,  315,  517], dog
 1:  48%, [ 180,  181,  564,  430], bicycle

Output:

_images/yolox_dla_out.jpgyolox_dla_out

附:其他

欢迎加入 QQ 群 829565581 来一起讨论!

C API

Initial

实现 Tengine 框架基础资源初始化、释放功能、版本号查询的功能。

示例:

/* inital tengine */
if (init_tengine() != 0)
{
    fprintf(stderr, "Initial tengine failed.\n");
    return -1;
}
fprintf(stderr, "tengine-lite library version: %s\n", get_tengine_version());

/* some codes */

/* release tengine */
release_tengine();

int init_tengine(void)

Brief:

  • Initialize the tengine, only can be called once.

Return:

  • 0: Success, -1: Fail.

void release_tengine(void)

Brief:

  • Release the tengine, only can be called once.

const char* get_tengine_version(void)

Brief:

  • Get the version of the tengine.

Return:

  • const char * of version string.

Graph

实现 Tengine 计算图创建、释放、参数获取等功能。

/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_FP32;
opt.affinity = affinity;

/* create graph, load tengine model xxx.tmfile */
graph_t graph = create_graph(NULL, "tengine", model_file);

/* set the shape, data buffer of input_tensor of the graph */
tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0);

/* prerun graph, set work options(num_thread, cluster, precision) */
prerun_graph_multithread(graph, opt);

/* run graph */
run_graph(graph, 1);

/* get the result of classification */
tensor_t output_tensor = get_graph_output_tensor(graph, 0, 0);

/* release tengine */
postrun_graph(graph);
destroy_graph(graph);

graph_t create_graph(context_t context, const char* model_format, const char* file_name, ...)

Brief:

  • Create the run-time graph for execution from a saved model. If model format is NULL, an empty graph handle will be returned.

Params:

  • context: The context the graph will run inside could be NULL and the graph is created in a private context

  • model_format: The model format type,such as "caffe","tengine"

  • file_name: The name of model file.

Return:

  • 0: Success, -1: Fail.

int prerun_graph_multithread(graph_t graph, struct options opt)

Brief:

  • Initialize resource for graph execution, and set cluster and threads count will used.

Params:

  • graph: The graph handle.

  • opt: The graph exec options

Return:

  • 0: Success, -1: Fail.

int run_graph(graph_t graph, int block)

Brief:

  • Execute graph.

Params:

  • graph: The graph handle.

  • block: Blocking or nonlocking.

Return:

  • 0: Success, -1: Fail.

int postrun_graph(graph_t graph)

Brief:

  • Release the resource for graph execution.

Params:

  • graph: graph handle.

Return:

  • 0: Success, -1: Fail.

int destroy_graph(graph_t graph)

Brief:

  • Destory the runtime graph and release allocated resource.

Params:

  • graph: The graph handle.

Return:

  • 0: Success, -1: Fail.

int set_graph_layout(graph_t graph, int layout_type)

Brief:

  • Set the layout type of the graph the default layout of graph is NCHW.

Params:

  • graph, the graph handle

  • layout_type, the layout type NCHW or NHWC

Return:

  • 0: Success, -1: Fail.

int set_graph_input_node(graph_t graph, const char* input_nodes[], int input_number)

Brief:

  • designate the input nodes of the graph.

Params:

  • graph: the graph handle

  • input_nodes: the node name list of input nodes

  • input_number: the number of input_nodes

Return:

  • 0: Success, -1: Fail.

int set_graph_output_node(graph_t graph, const char* output_nodes[], int output_number)

Brief:

  • designate the output nodes of the graph.

Params:

  • graph: the graph handle

  • output_nodes: the node name list of output nodes

  • output_number: the number of output_nodes

Return:

  • 0: Success, -1: Fail.

int get_graph_input_node_number(graph_t graph)

Brief:

  • Get the number of input node of the graph.

Params:

  • graph: The graph handle.

Return:

  • the input node number.

node_t get_graph_input_node(graph_t graph, int idx)

Brief:

  • Get the node handle of #idx of input node of the graph.

Params:

  • graph: The graph handle.

  • idx: The input node index,starting from zero.

Return:

  • The node name or NULL on error.

int get_graph_output_node_number(graph_t graph)

Brief:

  • Get the number of output node of the graph.

Params:

  • graph: The graph handle.

Return:

  • The input node number.

node_t get_graph_output_node(graph_t graph, int idx)

Brief:

  • Get the node handle #idx of a graph output node.

Params:

  • graph: The graph handle.

  • idx: The input node index, starting from zero.

Return:

  • The node name or NULL on error.

tensor_t get_graph_output_tensor(graph_t graph, int output_node_idx, int tensor_idx)

Brief:

  • Get a tensor handle of a graph output node.

Params:

  • graph: The graph handle.

  • output_node_idx: The output node index.

  • tensor_idx: The output tensor index of the output node.

Return:

  • The tensor handle or NULL on error.

tensor_t get_graph_input_tensor(graph_t graph, int input_node_idx, int tensor_idx)

Brief:

  • Get a tensor handle of a graph output node.

Params:

  • graph: The graph handle.

  • input_node_idx: The input node index, starting from zero.

  • tensor_idx: The output tensor index of the input node, starting from zero.

Return:

  • The tensor handle or NULL on error.

Node

Node 节点相关操作。

node_t create_graph_node(graph_t graph, const char* node_name, const char* op_name)

Brief:

  • Create a node for the graph.

Params:

  • graph: The graph handle.

  • node_name: The name of the node.

  • op_name: The name of the operate.

Return:

  • The node handle or NULL on error.

node_t get_graph_node(graph_t graph, const char* node_name)

Brief:

  • Get the node handle of the graph.

Params:

  • graph: The graph handle.

  • node_name: The name of the node.

Return:

  • The node handle or NULL on error.

Tensor

Tensor 数据相关操作。

/* set the shape, data buffer of input_tensor of the graph */
int img_size = img_h * img_w * 3;
int dims[] = {1, 3, img_h, img_w};    // nchw
float* input_data = ( float* )malloc(img_size * sizeof(float));

tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0);
set_tensor_shape(input_tensor, dims, 4);
set_tensor_buffer(input_tensor, input_data, img_size * 4);
 
/* get the result of classification */
tensor_t output_tensor = get_graph_output_tensor(graph, 0, 0);
float* output_data = ( float* )get_tensor_buffer(output_tensor);
int output_size = get_tensor_buffer_size(output_tensor) / sizeof(float);

tensor_t create_graph_tensor(graph_t graph, const char* tensor_name, int data_type)

Brief:

  • create a tensor handle by tensor name.

Params:

  • graph: The graph handle

  • tensor_name: Tensor name.

  • data_type: the data type.

Return:

  • The tensor handle or NULL on error.

tensor_t get_graph_tensor(graph_t graph, const char* tensor_name)

Brief:

  • Get a tensor handle by tensor name.

Params:

  • graph: The graph handle

  • tensor_name: Tensor name.

Return:

  • The tensor handle or NULL on error.

const char* get_tensor_name(tensor_t tensor)

Brief:

  • Get the name of the tensor handle.

Params:

  • tensor: the tensor handle.

Return:

  • const char * of version string.

int get_tensor_shape(tensor_t tensor, int dims[], int dim_number)

Brief:

  • Get the shape of tensor.

Params:

  • tensor: The tensor handle.

  • dims: An int array to get the returned shape.

  • dim_number: The array size.

Return:

  • >=1 the valid dim number, or -1 Fail.

int set_tensor_shape(tensor_t tensor, const int dims[], int dim_number)

Brief:

  • Set the shape of tensor.

Params:

  • tensor: The tensor handle.

  • dims: An int array to get the returned shape.

  • dim_number: The array size.

Return:

  • 0: Success; -1: Fail.

Device

Exection context

设置执行会话模块相关操作,主要用于显示设置各种异构计算的硬件后端。

/* create VeriSilicon TIM-VX backend */
context_t timvx_context = create_context("timvx", 1);
int rtt = add_context_device(timvx_context, "TIMVX");

/* create graph, load tengine model xxx.tmfile */
graph_t graph = create_graph(timvx_context, "tengine", model_file);

context_t create_context(const char* context_name, int empty_context)

Brief:

  • Create one execution context with name.

Params:

  • context_name: The name of the created context.

  • empty_context: No device is assigned with this context otherwise, all proved devices will be added into the context.

Return:

  • Execution context handle. If create Failed, return NULL.

int add_context_device(context_t context, const char* dev_name)

Brief:

  • Add a device into one context.

Params:

  • context: The context handle.

  • dev_name: The device name.

Return:

  • 0: Success, -1: Fail.

void destroy_context(context_t context)

Brief:

  • Destory and reclaim the resource related with the context.

Params:

  • context: The context handle.

Misc

其他辅助 API

/* set the level of log with INFO */
set_log_level(LOG_INFO);

/* dump the graph to console */
dump_graph(graph);

void set_log_level(enum log_level level)

Brief:

  • Set the logger level.

Params:

  • level: The log level.

void dump_graph(graph_t graph)

Brief:

  • Dump the run-time graph. If the graph is dumpped after prerun(), it will dump the optimized graph instead of the origin one.

Params:

  • graph: The graph handle.

Plugin

宏定义

结构体

自定义算子

架构详解


设计背景

先行者 Tengine

最早的 Tengine 是使用 C++ 分层设计的,编译采用 make,也就是嵌入系统常常采用的 makefile,编写之初支持的硬件是 CPU。可扩展性是 Tengine 和现在的 Lite 版本的设计核心,不仅仅是 Operator 能够很方便的通过注册宏扩展,各个主要模块也是扩展的。

推出 Tengine 发展一段时间后,市场反馈需要 Tengine 支持 MCUVLIW 架构的 DSP;在一些项目中,编译工具链对 C++ 较新标准支持较差,甚至是没有 C++ 编译器,这就对 Tengine 的架构产生了新的要求。

在这种背景下,项目团队提出重新设计代号 LiteTengineC 语言的新主要分支,经过一段时间的开发后,后续项目陆续切换到 Tengine Lite 上。从能力上,Lite 和原分支是一样的。经过一段时间后,全部 Tengine 项目维护周期终结后,Tengine 分支将会进入存档冻结状态,不再维护。

继任者 Tengine Lite

“薪火相传 砥砺前行”,Tengine Lite 的早期版本主题设计和 Tengine 高度相似,较早的 Tengine Lite 版本注册机制依赖 GNU C 扩展,而 GNU C 扩展并不是标准 C 的内容。当社区呼唤需要扩展支持到非 GCC 系列的场景,如 Microsoft Visual Studio 上时,遇到了较多的困难。另一方面,纯 C 的设计使得 Tengine Lite 的入手难度较高,社区和项目反馈需要提高易用性,“Tengine 从入门到‘放弃’”的时间要“显著”缩短。

经过一段时间的磨合后,团队决定重新设计主要模块,重点是解决这几方面的问题。

架构设计

重新设计的设计目标之一是,采用纯 C 设计 TengineLite 分支(以下简称 Tengine,不再强调 Lite 分支)最小 Runtime,复杂的功能和逻辑可以使用 C++ 开发。这样保持纯 C Runtime 在局限场景下优势的同时,使用 C++ 开发复杂模块,使注意力集中在开发算法本身上。

架构概览

_images/architecture-1.png

重要模块介绍

网络描述 graph 和相关结构体

source/graph 目录下是 Convolution Neural Network 的描述结构体和函数。

/*!
 * @struct ir_graph_t
 * @brief  Abstract graph intermediate representation
 */
typedef struct graph
{
    struct tensor** tensor_list;            //!< the tensor list of a graph
    struct node**   node_list;              //!< the node list of a graph
    int16_t* input_nodes;                   //!< input nodes index array of a graph
    int16_t* output_nodes;                  //!< output nodes index array of a graph

    uint16_t tensor_num;                    //!< the count of all graph tensor
    uint16_t node_num;                      //!< the count of all graph node
    uint16_t input_num;                     //!< input nodes index count of a graph
    uint16_t output_num;                    //!< input nodes index count of a graph

    int8_t   graph_layout;                  //!< the data layout of a graph
    int8_t   model_layout;                  //!< model layout of graph source model
    int8_t   model_format;                  //!< model format of graph source model

    uint8_t  status;                        //!< the status of graph

    struct   serializer* serializer;        //!< serializer of graph
    void*    serializer_privacy;            //!< privacy data of serializer

    struct   device* device;                //!< assigned nn_device for this graph
    void*    device_privacy;                //!< privacy data of device

    struct   attribute*  attribute;         //<! attribute of graph

    struct vector* subgraph_list;           //!< subgraph list of this graph
} ir_graph_t;

网络的主体 DAGgraph 和其中的 node 节点共同描述;全部需要的 nodenode->node_list 中存储;node 主要描述了该节点的依赖 tensoroperator 情况。所有需要的 tensorgraph->tensor_list 中存储。graph->node_numgraph->tensor_num 分别描述了 graphnodetensor 数量。数据(tensor) + 操作(operator)构成的节点(node)最后构成一个完整的图(graph),根据调度器(scheduler)模块的分配,形成一个完整的运行图运行在 CPU 设备(device)上,这也是典型的 CPUCNN 模型的应用场景。

/*!
 * @struct ir_node_t
 * @brief  Abstract node intermediate representation
 */
typedef struct node
{
    uint16_t  index;            //!< the index of a node
    uint8_t   dynamic_shape;    //!< flag of dynamic shape
    uint8_t   input_num;        //!< count of input tensor
    uint8_t   output_num;       //!< count of output tensor
    uint8_t   node_type;        //!< type of node: { input, output, intermediate }
    int8_t    subgraph_idx;     //!< id of the owner subgraph

    uint16_t* input_tensors;    //!< id array of input tensor
    uint16_t* output_tensors;   //!< id array of output tensor

    char* name;                 //!< name of a node

    struct op op;               //!< operator of a node
    struct graph* graph;        //!< pointer of the related graph
} ir_node_t;

node 是网络的节点,不同功能的 node 协助完成数据准备和计算的工作。node->input_tensorsnode->output_tensors 分别描述了一个 node 的输入输出 tensor 的索引 index,结合 node->input_numnode->output_num 就可以完成节点的遍历。实际的 node 是存储在 graph->node_list 中的。

/*!
 * @struct ir_tensor_t
 * @brief  Abstract tensor intermediate representation
 */
typedef struct tensor
{
    uint16_t index;                          //!< the index of a tensor
    int16_t  producer;                       //!< node id, '-1' means no producer
    int16_t  consumer[TE_MAX_CONSUMER_NUM];  //!< consumer nodes array

    uint8_t  reshaped;                       //!< the tensor's shape has changed
    uint8_t  consumer_num;                   //!< count of consumer nodes
    uint8_t  tensor_type;                    //!< tensor_type: { const, input, var, dep }
    uint8_t  data_type;                      //!< data_type: { int8, uint8, fp32, fp16, int32 }
    uint8_t  dim_num;                        //!< count of dimensions
    uint8_t  elem_size;                      //!< size of single element
    uint8_t  subgraph_num;                   //!< count of all subgraph those will waiting this tensor ready
    uint8_t  free_host_mem;                  //!< should free host memory?
    uint8_t  internal_allocated;             //!< how memory is allocated?
    uint8_t  layout;                         //!< tensor layout: { TENGINE_LAYOUT_NCHW, TENGINE_LAYOUT_NHWC }

    uint16_t quant_param_num;                //!< quantization dimension
    uint32_t elem_num;                       //!< count of total elements
    int dims[TE_MAX_SHAPE_DIM_NUM];          //!< shape dimensions

    /*!
     * @union anonymity data pointer
     * @brief give useful pointer pointer
     */
    union
    {
        void*    data;
        int8_t*    i8;
        uint8_t*   u8;
        float*    f32;
        uint16_t*   f16;
        int32_t*  i32;
    };

    char* name;                             //!< tensor name

    /*!
     * @union anonymity quantization scale union
     * @brief scale or its array
     */
    union
    {
        float* scale_list;
        float  scale;
    };

    /*!
     * @union anonymity quantization zero point union
     * @brief zero point or its array
     */
    union
    {
        int  zero_point;
        int* zp_list;
    };

    struct dev_mem* dev_mem;
    uint8_t* subgraph_list;                 //!< subgraph index list of those subgraph will waiting this tensor ready
} ir_tensor_t;

tensornode 完成功能的数据基础。tensor->dims 描述了 tensorshapetensor->quant_param_num, tensor->scaletensor->scale_listtensor->zero_pointtensor->zp_list 三者共同描述了 tensor 的量化情况,具体的数值类型由 tensor->data_type 描述。 当用户通过 API 对函数进行修改后,tensor->free_host_memtensor->internal_allocated 会进行变化,用来区分是由内部释放还是用户手动释放。

/*!
 * @struct ir_op_t
 * @brief  Abstract operator intermediate representation
 */
typedef struct op
{
    uint16_t type;                          //!< the type of a operator
    uint8_t  version;                       //!< the version of a operator
    uint8_t  same_shape;                    //!< the flag of weather the operator will keep shape
    uint16_t param_size;                    //!< size of parameter memory buffer
    void* param_mem;                        //!< parameter memory buffer
    int (*infer_shape)(struct node*);       //!< infer(or broadcast) the shape from input to output(s)
} ir_op_t;

operatornode 完成功能的行为基础。op->type 描述了类型,这是这个 op 的行为基础。

/*!
 * @struct ir_subgraph_t
 * @brief  Abstract subgraph intermediate representation
 */
typedef struct subgraph
{
    uint8_t   index;                //!< the index of a subgraph
    uint8_t   input_ready_count;    //!< the count of all in ready input tensors
    uint8_t   input_wait_count;     //!< the count of all out of ready input tensors
    uint8_t   input_num;            //!< the count of input tensors
    uint8_t   output_num;           //!< the count of output tensors
    uint8_t   status;               //!< the execution status of subgraph

    uint16_t  node_num;             //!< the count of nodes in subgraph
    uint16_t* node_list;            //!< all nodes index list of subgraph

    uint16_t* input_tensor_list;    //!< input tensors index list of subgraph
    uint16_t* output_tensor_list;   //!< output tensors index list of subgraph

    struct graph*  graph;           //!< the pointer of the related graph

    struct device* device;          //!< the device which will the subgraph running on
    void*  device_graph;            //!< the related device graph
} ir_subgraph_t;

subgraph 是设备有关的,一个 subgraph 只工作于一个单一 device。当网络的全部 node 完全运行于单一 device 时,subgraph 就包含全部 graph 中的 node。当运行于多 device 时,根据实际 deviceoperator 支持情况,划分不同的 node 到多个不同的 subgraph 中。最小的 subgraph 只包含一个 node 节点;最大的 subgraph 有全部的 node

设备描述 device 和相关结构体

source/device 目录下是 Convolution Neural Network 的描述结构体和函数,子目录中是各种 device 的实现。其中 source/device/cpuTengine 支持的全部 CPU 的相关代码。

/*!
 * @struct nn_device_t
 * @brief  Abstract neural network runnable device description struct
 */
typedef struct device
{
    const char* name;
    struct interface* interface;      //!< device scheduler operation interface
    struct allocator* allocator;      //!< device allocation operation interface
    struct optimizer* optimizer;      //!< device optimizer operation interface
    struct scheduler* scheduler;      //!< device scheduler
    void*  privacy;                   //!< device privacy data
} ir_device_t;

device 是由嵌套的结构体构成的,结合描述了三类主要接口。关于详细细节和如何添加 device 还可以参考 扩展硬件后端 文档。

每当实现一个 device 的时候,都需要调用 int register_device(struct device* device); 函数,将 device 注册到 Tengine 中。运行时,可以通过 int add_context_device(context_t context, const char* dev_name);int set_context_device(context_t context, const char* dev_name, const void* dev_option, size_t dev_opt_size); 设置需要使用的 device,对于带有 optiondevice,更推荐用后一个接口一并设置 option。比如,在 TensorRT device 中,需要初始化时指定多个 GPU 以及推理精度时特别有用。CPU device 被假设为总是可用。

模型解析 serializer 和相关结构体
/*!
 * @struct serializer_t
 * @brief  Abstract serializer
 */
typedef struct serializer
{
    const char* (*get_name)(struct serializer*);

    //!< load model from file
    int (*load_model)(struct serializer*, struct graph*, const char* fname, va_list ap);

    //!< load model from memory
    int (*load_mem)(struct serializer*, struct graph*, const void* addr, int size, va_list ap);

    //!< unload model, free serializer and device related resource
    int (*unload_graph)(struct serializer*, struct graph*, void* s_priv, void* dev_priv);

    //!< interface exposed for register operator extension
    int (*register_op_loader)(struct serializer*, int op_type, int op_ver, void* op_load_func, void* op_type_map_func, void* op_ver_map_func);

    //!< interface exposed for register operator extension
    int (*unregister_op_loader)(struct serializer*, int op_type, int op_ver, void* op_load_func);

    //!< interface exposed for initialize serializer
    int (*init)(struct serializer*);

    //!< interface exposed for release serializer
    int (*release)(struct serializer*);
} serializer_t;

支持新的模型格式解析,只需要填充并注册一个 serializer 即可完成。Tengine 正计划通过此模块将主流的模型支持增加进来。

模块注册

Tengine 能够工作有赖于注册的各个模块,这些注册接口和位置可以参考 source/module 目录下的相关代码。

static vector_t* internal_serializer_registry = NULL;   //!< registry of model serializer
static vector_t* internal_device_registry     = NULL;   //!< registry of runnable neural network device
static vector_t* internal_op_method_registry  = NULL;   //!< registry of operators
static vector_t* internal_op_name_registry    = NULL;   //!< registry of operators name

source/module/module.c 中的 static struct vector 中可以知道, serializerdeviceoperaor 都被注册为 vector 中,在 module.h 中提供了注册和查找的函数。

/*!
 * @brief Register a serializer.
 *
 * @param [in]  serializer: The pointer to a struct of serializer.
 *
 * @return statue value, 0 success, other value failure.
 */
int register_serializer(struct serializer* serializer);


/*!
 * @brief Find the serializer via its name.
 *
 * @param [in]  name: The name of serializer.
 *
 * @return  The pointer of the serializer.
 */
struct serializer* find_serializer_via_name(const char* name);


/*!
 * @brief Find the serializer via its registered index.
 *
 * @param [in]  index: The index of serializer.
 *
 * @return  The pointer of the serializer.
 */
struct serializer* find_serializer_via_index(int index);


/*!
 * @brief Get count of all registered serializer.
 *
 * @return  The count of registered serializer.
 */
int get_serializer_count();


/*!
 * @brief Unregister a serializer.
 *
 * @param [in]  serializer: The pointer to a struct of serializer.
 *
 * @return statue value, 0 success, other value failure.
 */
int unregister_serializer(struct serializer* serializer);


/*!
 * @brief Release all serializer.
 *
 * @return statue value, 0 success, other value failure.
 */
int release_serializer_registry();


/*!
 * @brief Register a device.
 *
 * @param [in]  device: The pointer to a struct of device.
 *
 * @return statue value, 0 success, other value failure.
 */
int register_device(struct device* device);


/*!
 * @brief Find the device via its name.
 *
 * @param [in]  name: The name of device.
 *
 * @return  The pointer of the device.
 */
struct device* find_device_via_name(const char* name);


/*!
 * @brief Find the default device.
 *
 * @return  The pointer of the device.
 */
struct device* find_default_device();


/*!
 * @brief Find the device via its registered index.
 *
 * @param [in]  name: The index of device.
 *
 * @return  The pointer of the device.
 */
struct device* find_device_via_index(int index);


/*!
 * @brief Get count of all registered device.
 *
 * @return  The count of registered device.
 */
int get_device_count();


/*!
 * @brief Register a device.
 *
 * @param [in]  device: The pointer to a struct of device.
 *
 * @return statue value, 0 success, other value failure.
 */
int unregister_device(struct device* device);


/*!
 * @brief Release all device.
 *
 * @return statue value, 0 success, other value failure.
 */
int release_device_registry();


/*!
 * @brief Register an operator method.
 *
 * @param [in]  type: The type of an operator.
 * @param [in]  type: The name of an operator.
 * @param [in]  type: The method of an operator.
 *
 * @return statue value, 0 success, other value failure.
 */
int register_op(int type, const char* name, struct method* method);


/*!
 * @brief Find an operator method.
 *
 * @param [in]  type: The type of an operator method.
 * @param [in]  version: The version of an operator method.
 *
 * @return  The pointer of the method.
 */
struct method* find_op_method(int type, int version);


/*!
 * @brief Find an operator method via its registered index.
 *
 * @param [in]  index: The index of operator method.
 *
 * @return  The pointer of the operator method.
 */
struct method* find_op_method_via_index(int index);


/*!
 * @brief Find an operator name.
 *
 * @param [in]  type: The type of an operator method.
 *
 * @return  The char array of the method.
 */
const char* find_op_name(int type);


/*!
 * @brief Get count of all registered operator method.
 *
 * @return  The count of registered operator method.
 */
int get_op_method_count();


/*!
 * @brief Register an operator.
 *
 * @param [in]  type: The type of an operator method.
 * @param [in]  version: The version of an operator method.
 *
 * @return statue value, 0 success, other value failure.
 */
int unregister_op(int type, int version);


/*!
 * @brief Release all operator.
 *
 * @return statue value, 0 success, other value failure.
 */
int release_op_registry();

C 语言是没有 C++ 构造函数概念的,那么如果不做处理,就需要一套运行时注册和反注册的机制。在 Tengine v1.4 以后,注册的另一部分通过 CMake 完成。 通过一定规则扫描和收集到的文件,可以通过 cmake/registry.cmake 文件中添加的 GENERATE_REGISTER_HEADER_FILE(_REG_LEAD_STRING _DEL_LEAD_STRING _BACK_STRING _CONFIG_FILE _TARGET_FILE) 函数进行文件生成。

# generate needed registry
FUNCTION (GENERATE_REGISTER_HEADER_FILE _REG_LEAD_STRING _DEL_LEAD_STRING _BACK_STRING _CONFIG_FILE _TARGET_FILE)
    SET (_BGN_NOTICE_STR "// code generation start\n")
    SET (_END_NOTICE_STR "// code generation finish\n")

    SET (_GEN_REG_DEF_STR "${_BGN_NOTICE_STR}")
    SET (_GEN_REG_CAL_STR "${_BGN_NOTICE_STR}")
    SET (_GEN_DEL_DEF_STR "${_BGN_NOTICE_STR}")
    SET (_GEN_DEL_CAL_STR "${_BGN_NOTICE_STR}")

    FOREACH (_VAR ${ARGN})
        STRING(REGEX REPLACE ".+/(.+)\\..*" "\\1" _NAME ${_VAR})

        SET (_REG_FUNC "${_REG_LEAD_STRING}${_NAME}${_BACK_STRING}()")
        SET (_DEL_FUNC "${_DEL_LEAD_STRING}${_NAME}${_BACK_STRING}()")

        SET (_GEN_REG_DEF_STR "${_GEN_REG_DEF_STR}extern int ${_REG_FUNC};\n")

        SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}    ret = ${_REG_FUNC};\n")
        SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}    if(0 != ret)\n")
        SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}    {\n")
        SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}        TLOG_ERR(\"Tengine FATAL: Call %s failed(%d).\\n\", \"${_REG_FUNC}\", ret);\n")
        SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}    }\n")

        SET (_GEN_DEL_DEF_STR "${_GEN_DEL_DEF_STR}extern int ${_DEL_FUNC};\n")

        SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}    ret = ${_DEL_FUNC};\n")
        SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}    if(0 != ret)\n")
        SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}    {\n")
        SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}        TLOG_ERR(\"Tengine FATAL: Call %s failed(%d).\\n\", \"${_REG_FUNC}\", ret);\n")
        SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}    }\n")
    ENDFOREACH()

    SET (_GEN_REG_DEF_STR "${_GEN_REG_DEF_STR}${_END_NOTICE_STR}")
    SET (_GEN_REG_CAL_STR "${_GEN_REG_CAL_STR}    ${_END_NOTICE_STR}")
    SET (_GEN_DEL_DEF_STR "${_GEN_DEL_DEF_STR}${_END_NOTICE_STR}")
    SET (_GEN_DEL_CAL_STR "${_GEN_DEL_CAL_STR}    ${_END_NOTICE_STR}")

    CONFIGURE_FILE(${_CONFIG_FILE} ${_TARGET_FILE})
ENDFUNCTION()

这个函数的核心就是根据输入的文件列表,产生两个字符串 _GEN_REG_CAL_STR_GEN_DEL_CAL_STR,这两个字符串将会在生成过程 CONFIGURE_FILE(${_CONFIG_FILE} ${_TARGET_FILE}) 中,替换掉 header_name.h.in 中的 @_GEN_REG_CAL_STR@@_GEN_DEL_CAL_STR@ 字符串部分,分别完成函数的声明、注册函数的调用、反注册函数的调用,生成的文件一般会保存在 ${CMAKE_BINARY_DIR} 中的对应位置,这由函数的 _TARGET_FILE 指定。 以 serializerCMakeLists.txt 为例,生成函数调用如下:

# generate all serializer
GENERATE_REGISTER_HEADER_FILE("register_" "unregister_" "" "${_SRL_SRC_ROOT}/register.h.in" "${_SRL_BIN_ROOT}/register.h" "${_SRL_TM2_SRL_SOURCE}")

这样就完成了配置过程,生成的头文件进一步的在后续的编译过程中发挥作用。当用户使用静态分析功能的 IDE 审阅代码时,由于相关头文件没有生成,所以可能会发生无法跳转的情况。经过编译配置的 Microsoft Visual Studio Code 等在打开文件夹后,会启动 CMake 进行配置和生成,这时的头文件就会生成,也能进行跳转了。其他 Microsoft Visual StudioJetBrains CLionIDE 也可以完成配置和生成过程,推荐使用。

扩展硬件后端


背景知识

Tengine 在设计上将可扩展性作为第一优先级纳入考量,较早的版本注册机制依赖 GCC GNU 扩展,而 GNU 扩展并不是标准 C 的内容。当社区呼唤需要扩展支持到 Microsoft Visual Studio 上时,遇到了较多的困难。 在决定重新设计后,注册模块的易用性有了很大的提升。新的机制通过 CMake 额外的处理过程,取得类似遍历和注册的效果,完成模块的注册。具体的设计和改进可以参考架构详解中的重要模块介绍

Tengine 在设计上将所有可以运行 CNN 的硬件单元均视为设备,CPU 就是一个典型的设备,在所有的编译选项里,CPU 设备都是默认包含的。如果描述一个新设备并注册,通常意义上这潜在上意味着要求编译的 Tengine 支持异构设备切图(相关内容可以阅读混合设备部分);如果注册的设备也描述了混合精度的接口,那么设备还支持混合精度Tengine 通过一个嵌套的结构体完成一个设备的描述:

/*!
 * @struct nn_device_t
 * @brief  Abstract neural network runnable device description struct
 */
typedef struct device
{
    const char* name;
    struct interface* interface;      //!< device scheduler operation interface
    struct allocator* allocator;      //!< device allocation operation interface
    struct optimizer* optimizer;      //!< device optimizer operation interface
    struct scheduler* scheduler;      //!< device scheduler
    void*  privacy;                   //!< device privacy data
} ir_device_t;

从结构体 ir_device_t 上可以看出,设计上将一个设备(device)分成 6 部分,第一部分 name 描述了设备的名字,设备名字不允许重复;interface 描述了设备接口;allocator描述了设备相关子图的操作;optimizer 描述了切图和混合精度的接口;scheduler 描述了设备独特的调度接口。 以上接口通常不需要全部填充,Tengine 提供一组丰富的示例指导如何自定义并添加用户自己的设备。


添加自定义设备

创建目录,编写 CMakeLists 文件

首先在source/device创建一个以用户设备命名的文件夹,文件夹可以是用户的设备缩写或其他用户认为比较酷的名字(这里假设起名为TPU,那么目录就是source/device/tpu),并从其他已经实现的 device/xxx 目录中复制一份 CMakeLists.txt 文件到当前文件夹;现在只需要对此 CMakeLists.txt 做些微的修改,而不需要从头创建。我们以从 source/device/acl/CMakeLists.txt 复制一份为例进行说明。该文件完整示例如下:

# 0. clear var
UNSET (_DEV_ACL_HEADER_PATH)
UNSET (_ACL_BASE_SOURCE)
UNSET (_ACL_OPS_SOURCE)
UNSET (_DEV_ACL_DEVICE_SOURCE)
UNSET (_DEV_ACL_COMPILER_DEFINES)
UNSET (_DEV_ACL_COMPILER_OPTIONS)
UNSET (_DEV_ACL_LINKER_OPTIONS)
UNSET (_DEV_ACL_LINK_LIBRARIES)


# 1.  set source root path
SET(_ACL_ROOT ${CMAKE_SOURCE_DIR}/source/device/acl)


# 2.  add header file path
LIST (APPEND _DEV_ACL_HEADER_PATH      ${_ACL_ROOT})
LIST (APPEND _DEV_ACL_HEADER_PATH      ${CMAKE_SOURCE_DIR}/3rdparty/acl/include)


# 3.  add linking lib searching path
LIST (APPEND _DEV_ACL_LINK_PATH        ${CMAKE_SOURCE_DIR}/3rdparty/acl/lib)


# 4.  add source files
AUX_SOURCE_DIRECTORY("${_ACL_ROOT}"    _ACL_BASE_SOURCE)
AUX_SOURCE_DIRECTORY("${_ACL_ROOT}/op" _ACL_OPS_SOURCE)
LIST (APPEND _DEV_ACL_DEVICE_SOURCE    ${_ACL_BASE_SOURCE})
LIST (APPEND _DEV_ACL_DEVICE_SOURCE    ${_ACL_OPS_SOURCE})


# 5.  add build options for cpu device
# 5.1 is a gcc or clang like compiler
IF (TENGINE_COMPILER_GCC OR TENGINE_COMPILER_CLANG)
    IF (TENGINE_COMPILER_GCC AND (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER_EQUAL "6.1"))
        LIST (APPEND _DEV_ACL_COMPILER_OPTIONS -Wno-ignored-attributes)
    ENDIF()
ENDIF()


# 5.2 is Microsoft Visual C++
IF (TENGINE_COMPILER_MSVC)
ENDIF()


# 6.  add link options


# 7.  add link libs
LIST (APPEND _DEV_ACL_LINK_LIBRARIES   arm_compute)
LIST (APPEND _DEV_ACL_LINK_LIBRARIES   arm_compute_core)


# 8. set all to cmake cache
SET (TENGINE_ACL_HEADER_PATH       ${_DEV_ACL_HEADER_PATH}        CACHE INTERNAL  "Tengine Arm Compute Library device header files searching path"   FORCE)
SET (TENGINE_ACL_LINK_PATH         ${_DEV_ACL_LINK_PATH}          CACHE INTERNAL  "Tengine Arm Compute Library device link libraries searching path" FORCE)
SET (TENGINE_ACL_DEVICE_SOURCE     ${_DEV_ACL_DEVICE_SOURCE}      CACHE INTERNAL  "Tengine Arm Compute Library device main source files"             FORCE)
SET (TENGINE_ACL_COMPILER_DEFINES  ${_DEV_ACL_COMPILER_DEFINES}   CACHE INTERNAL  "Tengine Arm Compute Library about compiler defines"               FORCE)
SET (TENGINE_ACL_COMPILER_OPTIONS  ${_DEV_ACL_COMPILER_OPTIONS}   CACHE INTERNAL  "Tengine Arm Compute Library about compiler options"               FORCE)
SET (TENGINE_ACL_LINKER_OPTIONS    ${_DEV_ACL_LINKER_OPTIONS}     CACHE INTERNAL  "Tengine Arm Compute Library about linker options"                 FORCE)
SET (TENGINE_ACL_LINK_LIBRARIES    ${_DEV_ACL_LINK_LIBRARIES}     CACHE INTERNAL  "Tengine Arm Compute Library about link libraries"                 FORCE)


# 9. install device option
INSTALL (FILES ${_ACL_ROOT}/acl_define.h DESTINATION include/tengine RENAME acl_device.h)

首先需要将使用的 CMake 变量的前缀进行修改,以避免潜在的变量冲突;将所有的 ACL 替换为TPU;然后修改模块的搜索根路径 _TPU_ROOTsource/device/tpu

# 1.  set source root path
SET(_TPU_ROOT ${CMAKE_SOURCE_DIR}/source/device/tpu)

自定义设备常常需要一些额外的3rdparty依赖,在 _DEV_TPU_HEADER_PATH_DEV_TPU_LINK_PATH 中进行相应的修改;在 ACL 中,增加了 ACL 预编译库路径 ${CMAKE_SOURCE_DIR}/3rdparty/acl/lib

# 2.  add header file path
LIST (APPEND _DEV_TPU_HEADER_PATH      ${_TPU_ROOT})
LIST (APPEND _DEV_TPU_HEADER_PATH      ${CMAKE_SOURCE_DIR}/3rdparty/tpu/include)


# 3.  add linking lib searching path
LIST (APPEND _DEV_TPU_LINK_PATH        ${CMAKE_SOURCE_DIR}/3rdparty/tpu/lib)

源码搜集部分按实际情况修改即可。

# 4.  add source files
AUX_SOURCE_DIRECTORY("${_TPU_ROOT}"    _TPU_BASE_SOURCE)
AUX_SOURCE_DIRECTORY("${_TPU_ROOT}/op" _TPU_OPS_SOURCE)
LIST (APPEND _DEV_TPU_DEVICE_SOURCE    ${_TPU_BASE_SOURCE})
LIST (APPEND _DEV_TPU_DEVICE_SOURCE    ${_TPU_OPS_SOURCE})

接下来的部分是编译相关的选项,根据实际情况修改即可。Tengine 默认打开了 C/C++ 支持,并尝试打开标准到 C99/C++14,如果工具链不支持会降级为 C98/C++11;如果用户的代码有其他特殊要求可以根据情况调整 _DEV_TPU_COMPILER_DEFINES_DEV_TPU_COMPILER_OPTIONS,_DEV_TPU_LINKER_OPTIONS 这 3 个变量。

# 5.  add build options for cpu device
# 5.1 is a gcc or clang like compiler
IF (TENGINE_COMPILER_GCC OR TENGINE_COMPILER_CLANG)
ENDIF()


# 5.2 is Microsoft Visual C++
IF (TENGINE_COMPILER_MSVC)
ENDIF()


# 6.  add link options

根据实际情况调整链接库情况,修改 _DEV_TPU_LINK_LIBRARIES 变量。

# 7.  add link libs
LIST (APPEND _DEV_TPU_LINK_LIBRARIES   tpu_runtime)

汇总一下临时变量到模块接口变量,接口变量设计为 cache 的,以便跨模块进行传递(另一方面这也是不同 device 不应重名的原因)。

SET (TENGINE_TPU_HEADER_PATH       ${_DEV_TPU_HEADER_PATH}        CACHE INTERNAL  "Tengine TPU device header files searching path"   FORCE)
SET (TENGINE_TPU_LINK_PATH         ${_DEV_TPU_LINK_PATH}          CACHE INTERNAL  "Tengine TPU device link libraries searching path" FORCE)
SET (TENGINE_TPU_DEVICE_SOURCE     ${_DEV_TPU_DEVICE_SOURCE}      CACHE INTERNAL  "Tengine TPU device main source files"             FORCE)
SET (TENGINE_TPU_COMPILER_DEFINES  ${_DEV_TPU_COMPILER_DEFINES}   CACHE INTERNAL  "Tengine TPU about compiler defines"               FORCE)
SET (TENGINE_TPU_COMPILER_OPTIONS  ${_DEV_TPU_COMPILER_OPTIONS}   CACHE INTERNAL  "Tengine TPU about compiler options"               FORCE)
SET (TENGINE_TPU_LINKER_OPTIONS    ${_DEV_TPU_LINKER_OPTIONS}     CACHE INTERNAL  "Tengine TPU about linker options"                 FORCE)
SET (TENGINE_TPU_LINK_LIBRARIES    ${_DEV_TPU_LINK_LIBRARIES}     CACHE INTERNAL  "Tengine TPU about link libraries"                 FORCE)

如果设备有特殊选项,可以考虑将其插入到 install 阶段。

# 9. install device option
INSTALL (FILES ${_TPU_ROOT}/tpu_define.h DESTINATION include/tengine RENAME tpu_device.h)

在根目录下的 CMakeLists.txt 中添加 option 以便编译时条件打开。

OPTION (TENGINE_ENABLE_TPU "With Awesome TPU support" OFF)

还需要修改 source/device/CMakeLists.txt 添加 Option 相关的处理。

# Awesome TPU
IF (TENGINE_ENABLE_TPU)
    ADD_SUBDIRECTORY (tpu)

    LIST (APPEND _TENGINE_DEVICE_HEADER_PATH        ${TENGINE_TPU_HEADER_PATH})
    LIST (APPEND _TENGINE_DEVICE_LINK_PATH          ${TENGINE_TPU_LINK_PATH})
    LIST (APPEND _TENGINE_DEVICE_COMPILER_DEFINES   ${TENGINE_TPU_COMPILER_DEFINES})
    LIST (APPEND _TENGINE_DEVICE_COMPILER_OPTIONS   ${TENGINE_TPU_COMPILER_OPTIONS})
    LIST (APPEND _TENGINE_DEVICE_LINKER_OPTIONS     ${TENGINE_TPU_LINKER_OPTIONS})
    LIST (APPEND _TENGINE_DEVICE_LINK_LIBRARIES     ${TENGINE_TPU_LINK_LIBRARIES})
    LIST (APPEND _TENGINE_DEVICE_SOURCE             ${TENGINE_TPU_DEVICE_SOURCE})
    LIST (APPEND _REGISTER_DEVICE_LIST              "${CMAKE_SOURCE_DIR}/source/device/tpu/tpu_device.cc")
ENDIF()

其中,_REGISTER_DEVICE_LIST 是设备注册的核心文件,需要根据实际情况进行填写。

填充结构体完成设备的注册

从某种意义上说,完成一个新设备的注册,只需要填充 ir_device_t 结构体,所有的其他代码工作都是围绕这个核心展开的。

/*!
 * @struct nn_device_t
 * @brief  Abstract neural network runnable device description struct
 */
typedef struct device
{
    const char* name;
    struct interface* interface;      //!< device scheduler operation interface
    struct allocator* allocator;      //!< device allocation operation interface
    struct optimizer* optimizer;      //!< device optimizer operation interface
    struct scheduler* scheduler;      //!< device scheduler
    void*  privacy;                   //!< device privacy data
} ir_device_t;

回顾 ir_device_t 结构体,struct interface 结构体描述了基本 API 接口:

/*!
 * @struct ir_interface_t
 * @brief  Abstract neural network runnable device interface struct
 */
typedef struct interface
{
    //!< interface of init this neural network device
    int (*init)(struct device* device);

    //!< interface of prepare runnable subgraph on device
    int (*pre_run)(struct device* device, struct subgraph* subgraph, void* options);

    //!< interface of run runnable subgraph on device
    int (*run)(struct device* device, struct subgraph* subgraph);

    //!< interface of post run runnable subgraph on device
    int (*post_run)(struct device* device, struct subgraph* subgraph);

    //!< interface of async run runnable subgraph on device
    int (*async_run)(struct device* device, struct subgraph* subgraph);

    //!< interface of async wait runnable subgraph on device
    int (*async_wait)(struct device* device, struct subgraph* subgraph, int try_wait);

    //!< interface of release runnable subgraph on device
    int (*release_graph)(struct device* device, void* device_graph);

    //!< interface of release this neural network device
    int (*release_device)(struct device* device);
} ir_interface_t;

参考 ACL,一个可能的 TPU 的实现填充如下:

static struct interface tpu_interface = {
        .init           = tpu_dev_init,
        .pre_run        = tpu_dev_prerun,
        .run            = tpu_dev_run,
        .post_run       = tpu_dev_postrun,
        .async_run      = nullptr,
        .async_wait     = nullptr,
        .release_graph  = nullptr,
        .release_device = tpu_dev_release,
};

tpu_dev_init() 是设备的全局初始化函数,注册设备时调用一次,反注册调用 release_device()。这个函数一般用来预申请设备内存作全局缓存,与设备驱动互操作初始化一些寄存器等。 tpu_dev_prerun() 是网络预处理部分,常见的处理包含申请 tensor 内存、转换数据 layout、创建设备运行图、编译设备 kernel 等。这部分申请的空间等需要在 tpu_release_graph() 中进行清理。 tpu_post_run()tpu_release_graph() 可能会引发混淆,tpu_post_run() 常常用来只是清除运行一次的相关状态,与 tpu_dev_prerun() 相反,真正的释放工作可以放到 tpu_release_graph() 中进行。一个可能的场景是,运行一次分辨率的模型 tpu_dev_prerun() 后,换一个分辨率前运行 tpu_post_run(),然后再运行 tpu_dev_prerun()。当需要真正销毁时,运行 tpu_release_graph()

ir_device_t 结构体中,struct interface 结构体描述了基本 API 接口, struct allocator 描述了设备能力上报接口和评估和调度的接口,struct optimizer 描述了切图和优化相关的接口,struct scheduler 描述了调度相关的接口。这几个接口的核心是 struct scheduler,设备并不总假设实现一个 struct scheduler,如果设备的这个接口描述是 nullptr,那么引擎会使用默认注册的 sync scheduler 运行网络,详情参考 source/scheduler/scheduler.c 中的 static ir_scheduler_t sync_scheduler。用户也可以实现一份自己的 struct scheduler 来完成特殊的任务;结合 struct allocatorstruct optimizer 可以产生丰富的可能。下面的描述是假设用户不实现 struct scheduler 的情况下的逻辑。

static struct allocator tpu_allocator = {
        .describe       = tpu_describe,
        .evaluation     = tpu_evaluation,
        .allocate       = tpu_allocate,
        .release        = tpu_release,
};

tpu_allocator 中,tpu_describe() 上报模型的 OP 支持情况和精度支持情况,这里的 OP 和精度支持的描述并不会随网络变化而改变,潜在的含义是这种状态下总是假设用户特定设备是 OP 或精度 全场景支持的。以卷积为例,这意味着用户的设备支持所有模式的卷积,无论 padstridehwc 情况如何。如果设备实现确实需要在运行时评估,那么可以自定 struct scheduler 完成自定义过程。 tpu_evaluation() 用来运行前评估一下已经实现的设备子图是否可运行;这在需要编译 kernel 时特别有用。 tpu_allocate() 用来支持设备存储池的相关内容,在默认 scheduler 下无需填充这个入口。tpu_release() 是相反的释放过程。

static struct optimizer tpu_optimizer = {
        .split_graph    = tpu_split_graph,
        .optimize_graph = nullptr,
};

tpu_optimizer 结构体中,tpu_split_graph() 用来实现切图,tpu_optimize_graph() 用来实现混合精度,其中 tpu_split_graph() 可以调用默认实现的 split_graph_node_to_sub_graph() 进行普通切图;如果有特殊需求可以结合其他结构体的不同字段形成组合。

最后,需要编写注册函数和反注册函数 int register_tpu_device()int unregister_tpu_device(),需要注意的是注册函数和反注册函数的后半段就是文件名,需要和实际文件名匹配,CMake 会自动的完成注册函数的调用过程的链接。

总结

通过上文的描述,可以知道添加一个自定义设备的核心工作就是填充 ir_device_t 结构体,描述完成后,设备注册的所有工作就完成了。模块化的 device 使得 Tengine 非常易于扩展,并有足够的灵活性。

彩蛋

init_tengine(void) 函数中,当 operator prototype 完成注册后,注册的就是 serializerdevices,但在静态代码状态下函数并不会跳转,用户可以安装一款集成开发环境,比如 Microsoft Visual StudioJetBrains CLion,打开文件夹后生成 CMake 过程后即可进行跳转。

OpenCL 后端添加自定义 OP 指南

0. 简介

OpenCL(Open Computing Language) 是第一个面向异构系统通用目的并行编程的开放式、免费标准,也是一个统一的编程环境,便于软件开发人员为高性能计算服务器、桌面计算系统、手持设备编写高效轻便的代码,而且广泛适用于多核心处理器(CPU)、图形处理器(GPU)、Cell 类型架构以及数字信号处理器(DSP)等其他并行处理器,在游戏、娱乐、科研、医疗等各种领域都有广阔的发展前景。

Tengine 已经完成 OpenCL 的支持和集成,在 ARM Mali GPU、NVIDIA GPU 上已经可以完成 Tengine 模型的 FP32 推理。关于 OpenCL 的算子支持及性能优化也在持续进行中。

Tengine 的 DAG 内存结构主要包含三个部分,graph(存储完整的模型架构),node(存储每个 OP 节点的参数信息),tensor(存储每个 OP 节点的输入输出张量)。 Tengine 的 OpenCL 后端主要通过 graph 对接 OpenCL 的执行队列,通过 tensor 的 data 对接 OpenCL 的内存分配,以及通过 node 的参数,完成 OpenCL 的 kernel 实现。大致映射关系如下图所示:

ir_graph                        >>>>>>>>>>>        OCLqueue(OpenCL的执行队列)
|
|---ir_tensor0                  >>>>>>>>>>>        cl_buffer0(输入buffer0)
|---    |       ir_tensor1      >>>>>>>>>>>            |___________cl_buffer1(输入buffer1)
|       |           |                                        |
|       |___________|                            |-----  Add OP Node 
|             |                                  |           |
|---       ir_node              >>>>>>>>>>>      |-----  Build OpenCL Kernel
|             |                                              |
|---      ir_tensor2            >>>>>>>>>>>              cl_buffer2(输出buffer)

为 OpenCL 后端添加新的算子主要包含以下 5 个步骤:

  1. 添加 tensor 映射函数。 为新添加OP的输入输出分配内存。OpenCL 的内存分配方式有两种,目前 Tengine 中,通过 OCLEngine::OCLTensorMap 函数实现了 buffer 的分配方式。如使用 buffer 这种方式,除 winograd 等需要分配额外内存的实现,其他实现已统一分配好内存,不需要再进行额外操作。如需要使用 image 的内存分配方式,则需在 OCLEngine::OCLTensorMap 函数中另外添加 create_image() 相关实现。

  2. 添加 node 映射函数。 为新添加 OP 的加入对应函数申明及实现,并完成 node 和 tensor 之间的关系衔接。

  3. 完成 OpenCL 的 kernel 实现。 为 node 的映射添加完整 .cl 的 kernel 实现。

  4. graph 映射。 完成 OpenCL 执行队列设置,及添加 OpenCL 所必须的参数设置,如 global_work_sizelocal_work_size 等。

  5. limit.hpp 文件中添加新增 OP 枚举。 切图机制需要获知后端 OP 支持情况,所以需要在 limit.hpp 文件中增加新的 OP 支持声明。

下面将介绍新OP算子添加的具体操作细节,以下内容除单独强调外,都假设在 <tengine-lite-root-dir>/source/device/opencl 目录下进行

1. 添加 tensor 映射

1.1 OpenCL 内存分配方式选择

选择对应的 OpenCL 内存分配方式,buffer 或 image。如选择 image 方式,则在 OCLEngine::OCLTensorMap 函数中添加对应实现,如选择 buffer 方式,这一步不需要进行额外操作。

1.2 OpenCL 内存分配大小设置

如果内存分配大小与 OP 输入输出 shape 一致,这一步不需要进行额外操作。如果 kernel 为类似 winograd 这样需要分配额外的内存做缓存的类型,则需要在 OCLEngine::OCLTensorMap 函数中添加对应内存分配实现。如果不能自动销毁,还需要注意添加析构或释放相关的实现。

2. 添加 node 映射

下面以 Dropout 算子为例,对流程进行说明。

2.1 添加 AddNode 函数申明

ocl_executor.hpp 中添加

bool AddDropoutNode(struct node* ir_node);

ocl_executor.cc 文件中的 OCLEngine::BuildKernel(struct subgraph* subgraph) 函数中添加

case OP_DROPOUT:
    this->AddDropoutNode(ir_node);
    break;

需要注意,OP_DROPOUT 是 Tengine 枚举的 OP 类型,其余枚举类型可以参考 <tengine-lite-root-dir>/source/operator/op.h 文件。

2.2 添加 OP 函数实现

./op 文件夹下添加 ocl_droput.cc 内容为 AddDropoutNode(ir_node) 的函数实现。 在 ./cl 文件夹下添加 drouput.cl 内容为 OpenCL 的 kernel 实现。

其中,AddDropoutNode(ir_node) 函数需要包含以下内容:

2.2.1 添加 .cl 文件路径
char* cl_env = getenv("ROOT_PATH");
char cl_kernel_path[500] = "";
strcat(cl_kernel_path, cl_env);
strcat(cl_kernel_path, "/source/device/opencl/cl/dropout.cl");
this->build_kernel(&cl_kernel_path[0], "dropout");
2.2.2 添加 .cl 文件对应 kernel 的参数
int arg_idx = 0;
CHECK_SET_KERNEL_STATUS(clSetKernelArg(kernel, arg_idx, sizeof(cl_mem), (void *)&this->ocl_tensor_map[output_tensor->index]));
CHECK_SET_KERNEL_STATUS(clSetKernelArg(kernel, ++arg_idx, sizeof(cl_mem), (void *)&this->ocl_tensor_map[input_tensor->index]));
CHECK_SET_KERNEL_STATUS(clSetKernelArg(kernel, ++arg_idx, sizeof(int), &output_tensor->elem_num));
2.2.3 设置 OpenCL kernel 执行队列
struct OCLqueue Dropout;
Dropout.name = "Dropout";
Dropout.dims = 1;
Dropout.queue_kernel = this->kernel;
Dropout.queue_global_work_size = (size_t*)malloc(sizeof(size_t));
Dropout.queue_global_work_size[0] = output_tensor->elem_num;
Dropout.queue_local_work_size = (size_t*)malloc(sizeof(size_t));
Dropout.queue_local_work_size[0] =  1;
this->queue_list.push_back(Dropout);

3. 添加 OpenCL 的 kernel 实现

完成.cl后缀的kernel实现。

4. 添加OpenCL可支持op

./ocl_limit.hpp文件中,找到对应的 OP 枚举,打开注释,标明已经实现了支持。


经过以上流程,一个 OP 的实现就已经完成了。接下来需要在实际 GPU 或其他 OpenCL 设备上运行评估性能,并进行适当调优,逐渐接近理论性能上限。

Road map

每个季度更新一次 Flag。

2021Q2

  • [x] refactor the framework code

  • [x] refactor the NPU plugin code

  • [x] support VS2017 compile

  • [ ] support macOS compile

  • [x] support the mode type of PaddlePaddle

  • [ ] add more examples with NPU platform

  • [ ] fix the Float32 bugs of Vulkan