故事
起初,一帮科学家用Python实现神经网络模型,为了方便开发了Python库:caffe(Convolutional Architecture for Fast Feature Embedding)、TensorFlow、MXNet、Keras、Pytorch。这些个库是Python Wrapper + C/C++ Code,跑在CPU上很慢,借助CUDA能用上NVIDIA GPU加速(就是贵)。
这些个库,被称作深度学习框架,可能会有各自的 primitive operator set(元算子集),模型最后都可以表达成元算子组成的计算图。CUDA提供把这些元算子转换成适合在NVIDIA GPU上运行的函数形式,NVIDIA很下功夫,从底层支持。
之后呢,创造了一堆专用处理器,被冠名为NPU,指令集各家都不一样,而且没钱没人去做NVIDIA做的那一套东西。这没法从底层支持了,那就设法创造一个统一的表达方式比如ONNX,开发工具把用其他框架开发的模型转换成ONNX的表达方式,两者的表达方式是否能达到完美匹配本身就是个费钱费人的事,怎么主动吸引其他框架去支持ONNX才是关键,可惜各怀心事、目前是各自为大、为了开源的面子支持一下。
各方势力的合作和竞争下,很多模型都能转换成ONNX,够用了。于是小微做NPU公司,从ONNX出发,开发自己的Compiler Tool Kit。
这个Compiler Tool Kit也有开源免费的呀,比如TVM,对TVM做二次开发多省钱呀。
看看这个软件组成,垂直角度上看是一层叠一层的结构,从水平角度上看是一环连一环的结构。
知识点
Quantization(量化)这个操作纯粹是为了定点运算(fixed-point operation)比浮点运算(floating-point operation)跑的快、存的少这个目的而发明的。自然缺点就是从某个运算角度上看精度降低了,但只要从整个业务上看性能(performance)的下降可忽略就是可行的;虽然一些运算的精度降低了,可气人的是业务依然完成的很坚挺,气人不。
量化这一步放到了TVM里了,这个安排的时机有点奇怪,但是考虑到毕竟只是为了在NPU上跑的更快的目的去的,不关模型设计的事,就只能是谁需要谁干活了。
拿ONNX为例,TVM把ONNX的计算图转换成自己内部的IR(Intermediate Representation)表达形式,自然能想到TVM也有自己的一套元算子集,那就针对每个元算子或者元算子的组合去做自动量化,这个有个必要的前提就是模型的数据集要足够多和典型,这样才能保证,对某个变量采集到了足够的样本,足够的样本提取出合适的数值统计量(比如最大值、最小值、均值、方差等),合适的数值统计量才能保证合适的量化的参数。
常见的量化操作如下所示:
v
i
=
sat
(
round
(
v
f
−
v
m
i
n
v
m
a
x
−
v
m
i
n
⋅
2
n
)
)
v_i = \text{sat} \left( \text{round} \left( \frac{v_f - v_{min}}{v_{max} - v_{min}} \cdot 2^{n} \right) \right)
vi=sat(round(vmax−vminvf−vmin⋅2n))
其中,
v
i
v_i
vi 代表 fixed-point number;
v
f
v_f
vf 代表 floating-point number,
v
f
∈
[
v
m
i
n
,
v
m
a
x
]
v_f \in [v_{min}, v_{max}]
vf∈[vmin,vmax];
n
n
n 代表要保留的小数部分的bit数。
自然,反量化(inverse-quantization)的操作就是:
v
f
=
v
i
2
n
⋅
(
v
m
a
x
−
v
m
i
n
)
+
v
m
i
n
v_f = \frac{v_i}{2^n} \cdot (v_{max} - v_{min}) + v_{min}
vf=2nvi⋅(vmax−vmin)+vmin
知识点
TVM(Tensor Virtual Machine),就是个Compiler,负责把作为输入的模型的ONNX形式转换成chip specific executable(NPU上的可执行程序)。
Compiler本身就是负责把一个procedure从一种形式转换成另一种形式,分为前端(frontend)、优化器(optimizer)和后端(backend)。
TVM的前端负责把ONNX转换成其内部的表达形式:Relay.IR,还有负责量化。
优化器和后端是与NPU 指令集相关的,这才是各家研究的重点,而且做的东西不好使。