前言

安霸CVflow处理器单元是一个为视觉算法设计的的硬件协处理器(VPU/NPU),使用有向无环图(DAG,directed acyclic graph)来表示视觉算法。

概念介绍

DAG

DAG是一个有向无环图,视觉算法的计算流的图表示。图中的**节点(Nodes)**表示运算操作,连线(Links)表示运算操作之间的数据流。数据由多维张量组成,这里简称为向量(Vectors)

CVflow

DAG表示的视觉算法在CVflow处理器上执行。DAG包含了关于I/O buffers、Nodes(又称为Primitives)以及Links的描述符。

CVflow上没有程序计数器或者指令pipeline的概念,一个主调度器会基于图中的数据依赖关系对可用的硬件资源上的原语(primitives)进行分时复用的调度。在CVflow上,如果下游原语依赖的数据已经准备好,那么该原语会基于硬件可用与否被并行执行。这种部分执行(partial execution)允许更有效地调度硬件资源,并允许片上内存在原语之间传递结果,而不是从外部DRAM存储和重新加载这些结果。

原语之间的依赖关系在DAG程序中显式地表示,而不是由程序中指令的执行顺序所暗示。这种方法与传统处理器中的指令流不同,传统处理器中的指令通过寄存器文件、全局状态寄存器或片上内存共享状态信息。

为了便于数据的处理,CVflow处理器根据其输入和输出的维度自动迭代。而其他处理器的多维数据处理通常需要嵌套循环和特定的轮数。

CVflow处理器支持可变大小的4D张量(简称为Vectors)。运算操作直接在整个向量上执行,而不像其他传统处理器,数据类型是标量或着特定长度的SIMD数据类型。

Vectors

CVflow处理器中的数据被表示为多维张量,简称为向量(Vectors)。支持的维度有标量、1D、2D、3D或4D。接受的数据精度格式为8、16和32位整数、定点数或浮点数。

Primitives

原语是DAG中抽象节点的基本形式。节点对输入向量中包含的数据进行计算,并生成新的向量。

Descriptors

数据描述符(Data Descriptors)指定DAG中的数据链接(Data Links)的名称、向量类型和大小。

DAG程序由完整描述DAG功能的原语和数据描述符列表组成。

工具

# 环境设置
source /opt/amba/env/env_set.sh cv22 /opt/amba

vas:编译器

Vas是编译DAGs的编译器。Vas可以把.vas文本文件中的DAGs编译成二进制的.vdg对象文件。在编译过程中,vas还会检查输入的.vas文件的VAS语法的有效性、跨向量维度的输入和输出数据的一致性,以及基于当前芯片版本的配置要求。

作为编译的一部分,vas在VMEM中分配数据缓冲区,并生成微码头文件(microcode header files)、用于DAG可视化的.dot文件和DAG memory image的二进制转储(binary dump)。

vas -h
vas xxx.vas

vplot:可视化

Vplot是一个用来可视化由vas生成的.dot文件的脚本。Vplot以PDF格式输出DAG可视化。

vplot xxx.dot

ades:仿真器

Ades是CVflow处理器的仿真器,它允许DAG设计者测试他们的代码的算法正确性。

有两种执行模式:正常模式和交互模式。在正常模式下,ades通过一个脚本文件在单个批处理中运行所有预设的指令。在交互模式中,命令行shell提供了指令,允许通过可视化DAG中的中间向量进行交互调试。

#mode 1
ades xxx.cmd # 正常模式执行脚本
#mode 2
ades # 进入交互模式
script xxx.cmd # 执行脚本

其中xxx.cmd文件是一组ades指令的集合,通常由VDG文件和输入数据文件的加载指令、运行指令以及将结果存储到输出数据文件或转储中间数据的指令组成。一个例子如下:

ld out/vas_output/hello.vdg
lb input input/input.bin
run
sb output_0 output/output_0.bin

在交互模式下执行脚本后,可以通过vecinfopeekvec指令监视DAG中的向量。可以在交互模式下使用help命令查看所有的ades指令。

实用工具

cv_f2vp_convert

此脚本用于按照CVflow处理器支持的数据格式生成常量和即时二进制数据,可以通过cv_f2vp_convert -h命令查看用法,下面是一个将输入文件中的值转换成8-bit二进制表示的例子:

cv_f2vp_convert -it -fmt 0,0,0,0 -i zero.txt -o zero.bin

其中-it表示输入为text file, -fmt指定数据格式<sign,datasize,expoffset,expbits>zero.txt文件的内容如下:

1 1 1 1
0

第一行指定了数据维度(不是必须的,若没有,使用-nd参数),下面是数据,这里只有一个0,输出的zero.bin将只含有一个8-bit 0值的二进制表示。

编程概述

DAGs使用一种被称为VAS的DAG-based语言编写。DAG由两种类型的基本组件组成:数据链接描述符(Links)和原语(Primitives/Nodes)。

VAS编程语言提供了一组高度封装的宏,使得用户不必关心底层细节,只关注想要指定的信息。

VAS利用了C和C++的宏预处理器。因此,它支持C指令,如#define#ifdef等。

VAS语言支持的主要特性有:

  • 符号名称
  • C/C++ 风格的注释
  • 用户宏
  • 头文件

宏定义

编译器隐式地包含vp_vas.h,其中包含一组为CVflow原语、数据格式、向量维度和原语参数而定义的vas宏。它们被定义为变量宏

DAG顶层定义

遵循以下结构:

DAG <DAG_name> = {
VP_input(...);
...
VP_<primitive_type>(
...
VP_tensor(...),
...
);
...
VP_output(...);
}

DAG布局

生产者必须始终先于消费者

这是对DAG组件的合法布局的要求。DAG中的组件必须遵循数据流的顺序,生产者先于消费者。首先列出主输入描述符,然后是第一层原语,然后是使用第一层产生的数据的原语,以此类推。

每个原语的输出数据描述符的声明都嵌入在生产者原语的声明中。

对于使用前面原语的输出数据描述符的原语,通过数据描述符的标签来引用数据。

主输入描述符(Primary Input Descriptors)

动态主输入

VP_input(_name, _data_format, _vector, ...)

其中:

  • _name:此参数定义了与数据描述符相关联的标签。原语使用该名称将其输入链接到其各自的输入数据描述符。使用唯一的描述性名称;
  • _data_format:此参数定义了关联向量中的所有元素的数据类型和精度。可以使用预定义的公共数据类型。也可以使用data_format(_sign,_datasize,_expoffset,_expbits)宏,具体可见vp_vas.h
  • _vector:此参数定义了向量的维数。最高到4D:vector(planes, depth, height, width),若想定义布尔值vector(bitvector),需要在数据描述符中添加bitvector=1的属性;

静态主输入

VP_constant(_name, _data_format, _vector, _data_file, ...)

大部分参数与动态主输入一致,_data_file参数为二进制文件的名称,用双引号括起来。文件中的数据格式必须与_data_format规定的一致。

原语宏(Primitive Macros)

每个原语都由预定义的宏来描述。语法详情请参见vp_primitive.h

原语宏一般包含如下各项:

  • 输入描述符标签:标签可以是主输入描述符的名称或(前面的)原语输出描述符的名称;
  • 立即数张量描述符:用VP_imm()表示;
  • 输出张量描述符:用VP_tensor()表示;
  • 属性(attributes):用<name>=<value>表示;

立即数张量描述符(Immediate Tensor Descriptors)

// using string
VP_imm(_data, _data_format, _vector, ...)
// using file
VP_imm(_data_file, _data_format, _vector, ...)

其中:

  • _data:小端格式的十六进制字节的字符串。如"04 00"
  • _data_file:二进制文件的名称;
  • _data_format:指定关联向量中的所有元素的数据类型和精度的宏;
  • _vector:指定向量维度的宏;

输出张量描述符(Output Tensor Descriptor)

VP_tensor(_name, _data_format, [_vector,] ...)

其中:

  • _name:输出描述符的名称(标签)。

  • _data_format:指定关联向量中的所有元素的数据类型和精度的宏。

  • _vector:指定向量维度的宏(可选,用来检查编译器推断的输出维度是否正确)。

主输出描述符(Primary Input Descriptors)

VP_output(_name, _v0, ...)

其中:

  • _name:主输出的名称(标签)。
  • _v0:要分配给此主输出的原语输出描述符的名称。

标量(Scalars)

动态标量输入

VP_scalar(_name, _data_format, _value, ...)

其中:

  • _name:此参数定义了与数据描述符相关联的标签;
  • _data_format:此参数定义了标量的数据类型和精度;
  • _value:此参数定义了标量的数值;

静态标量输入

VP_scalar(_name, _data_format, _value, is_constant = 1, ...)

数字表示

常量标量或者立即数张量的标量可以直接表示成数字形式,如:

VP_add(1, 2.0, VP_tensor(x, u8(0)));

VPP扩展和优化

VAS编程语言支持自动张量构造(automatic tensor configuration)的扩展,也称为VPP扩展。使用-cnngen编译器选项启用这些扩展。

VPP扩展

  • 支持未定义数据类型的VP_tensor(_name, ...)
  • 支持未量化的数据类型(float32_t)的VP_imm("00 00 80 40", float32_t, vector(1));

VPP优化

VPP还作为一个预处理器,通过将基本原语(如muliaddiconv2i)融合或折叠成更有效的原语(如maddiconv2iepbs)来寻找优化原始vas代码的机会。

输入要求

VPP特性要求用户提供输入数据图像或图像序列,这将用于评估DAG中每个原语输出的动态范围(量化)。一个命令行的例子如下:

vas -cnngen example.vas -dra_input "data:data.bin"

如果使用多个图像,则图像序列以列表形式在文本文件中传递,每行一个文件名。

vas -cnngen example.vas -dra_input "data:data_list.txt"

生成的原语vas代码存储在./out目录下。最终的结果vas_output是这个目录下的一个子目录。

自动张量构造

自动张量构造是与VP_tensor()一起使用的,当未定义张量数据类型_data_format的情况下,则编译器就认为该张量的精度属性应当根据动态范围分析(dynamic range analysis,DRA)自动确定。同样_vector未定义的情况下,编译器自动确定张量维度,若定义,则作为检查自动确定的张量维度是否正确的依据。

其中_data_format也可以使用部分定义的形式如:

VP_tensor(x, data_format(1, 0, undef, 0))
// or
VP_tensor(x, i8(undef))

上面的例子就表示需要编译器自动确定的只有expoffset这一个属性。

自动立即数构造

自动立即数张量也是VPP扩展构造的一部分。用户仍然需要定义源数据的格式(目前仅支持float32_t)。立即数张量的最终数据格式将自动确定。即当指定数据格式为float32_t时,编译器会根据输入的float32_t数据自动确定张量的数据类型(8-bit或16bit),该格式可以适应整个直接张量的动态范围,同时最小化原始输出中的误差。

融合原语

通过-cnngen-opt选项可以将原语融合成更高效的原语(在-cnngen开启时)。