超全总结:神经网络加速之量化模型 | 附带代码

  1. 云栖社区>
  2. paperweekly>
  3. 博客>
  4. 正文

超全总结:神经网络加速之量化模型 | 附带代码

技术小能手 2018-06-01 14:46:43 浏览3830
展开阅读全文

量化模型(Quantized Model)是一种模型加速(Model Acceleration)方法的总称,包括二值化网络(Binary Network)、三值化网络(Ternary Network),深度压缩(Deep Compression)等。鉴于网上关于量化模型的不多,而且比较零散,本文将结合 TensorLayer 来讲解各类量化模型,并讨论一下我们过去遇到的各种坑。文章最后会介绍一些关于人工智能芯片的技术。

TensorLayer 是一个基于 TensorFlow 的高级开发工具,提供大量数据处理和建模 API,具备灵活性高、运行速度快等优点。今年 3 月,TensorLayer 提供了一套搭建量化网络的试验版本 API,不过目前这套 API 依然用矩阵乘法而不是加减或 bitcount 运算来加速(我们等会会提到)。

因此,这套 API 并不能加速,关于产品部署,目前可以用 TensorLayer 训练模型,然后用自定义的 C/C++ 实现的二值化计算(TensorLayer 有可能会提供一套额外的专门运行二值化网络的框架,并支持可以从 TensorLayer 那读取模型)。

注意,作为试验版本,这套 API 有可能会被修改。更多关于模型加速的技术,可关注:https://github.com/tensorlayer/tensorlayer/issues/416

Keywords:模型压缩(Model Compression),模型加速(Model Acceleration),二值化网络(Binary Network),量化模型(Quantized Model)

随着神经网络深度增加,网络节点变得越来越多,规模随之变得非常大,这是对移动硬件设备非常不友好的,所以想要在有限资源的硬件设备上布置性能良好的网络,就需要对网络模型进行压缩和加速,其中量化模型由于在硬件上移植会非常方便,在理论上来讲,是非常有发展潜力的。

比较有名气的量化模型有 Deepcompression,Binary-Net,Tenary-Net,Dorefa-Net,下面对这几种量化模型进行介绍。

DeepCompression

SongHan 这篇文章可以说是神经网络压缩领域开山之作,怎么说呢这篇文章很早就注意到了,也复现了,做了很多实验。也一直想用到硬件参数压缩以及模型加速当中,在这个过程中遇到了很多问题,现在提出来跟大家一起探讨。

算法整体框架如图:

02e7ef17bc95efe1243feeafa0b5c0e736d14e53

DeepCompression 主要分为三个主要的部分:剪枝,量化,哈夫曼编码,下面分别探讨这几种方法并且分析他们在硬件前向配置的加速潜力。

剪枝(purning):其实这个思路的核心非常简单,就是当网络收敛到一定程度的时候,作者认为阈值小于一定权重的权重对网络作用很小,那么这些权重就被无情的抛弃了。注意,是抛弃,彻底抛弃,在复现的时候这个地方是一个大坑,被剪掉的权重不会再接收任何梯度。

然后下面的套路简单了,就是很简单的将网络 reload,然后重新训练至收敛。重复这个过程,直到网络参数变成一个高度稀疏的矩阵。这个过程最难受的就是调参了,由于小的参数会不断被剪枝,为了持续增大压缩率,阈值必须不断增大,那么剩下的就看你的调参大法 6 不 6 了。

当初为了解决这个问题还专门设计了一个基于准确率损失和压缩率上升的公式,用于压缩。算是效果还可以,自己调参真的很难受。

最后参数会变成一个稀疏的矩阵,作者自己提出了一种编码方式:

eb10000a1b16d02be58ac1ccc831fdd4468b77a8

当压缩率低于一定的值时,编码解码开销其实是非常大的,甚至到一定范围,编码后的存储量甚至大于不压缩。

第二个就是量化了,将接近的值变成一个数。大概的思路如下:

957a83da22f361bc28f8275395ab58fcedee0259

需要注意的是,量化其实是一种权值共享的策略。量化后的权值张量是一个高度稀疏的有很多共享权值的矩阵,对非零参数,我们还可以进行定点压缩,以获得更高的压缩率。

论文的最后一步是使用哈夫曼编码进行权值的压缩,其实如果将权值使用哈夫曼编码进行编码,解码的代价其实是非常大的,尤其是时间代价。还需要注意的是,DeepCompression 中对于输出没有压缩。所以这种方案对于硬件加速的主要作用体现在遇到 0 即可 zero skip,即使用判断语句替代乘法器

Binary-Net

d47e62d2b349aca45e42305ed6714efbe5ed61d9Quantized Neural Networks: Training Neural Networks with Low Precision Weights and Activations
d47e62d2b349aca45e42305ed6714efbe5ed61d9Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1
d47e62d2b349aca45e42305ed6714efbe5ed61d9XNOR-Net: ImageNet Classification Using Binary Convolutional Neural Networks

通常我们在构建神经网络模型中使用的精度都是 32 位单精度浮点数,在网络模型规模较大的时候,需要的内存资源就会非常巨大,而浮点数是由一位符号位,八位指数位和尾数位三个部分构成的。完成浮点加减运算的操作过程大体分为四步:

1. 0 操作数的检查,即若至少有一个参与运算的数为零直接可得到结果;

2. 比较阶码大小并完成对阶;

3. 尾数进行加或减运算;

4. 结果规格化并进行舍入处理。

带来的问题是网络在运行过程中不仅需要大量的内存还需要大量的计算资源,那么 quantization 的优越性就体现出来了,在 2016 年发表在 NIPS 的文章 Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1 中,提出了利用降低权重和输出的精度的方法来加速模型,因为这样会大幅的降低网络的内存大小和访问次数,并用 bit-wise operator 代替 arithmetic operator。

下面具体介绍一下这种方法的原理,在训练 BNN 时,将权重和输出置为 1 或 -1,下面是两种二值化的方法:

第一种直接将大于等于零的参数置为 1,小于 0 的置为 -1;

33826bf8be00e8761cf5051c437da4366f78c9de

第二种将绝对值大于 1 的参数置为 1,将绝对值小于 1 的参数根据距离 ±1 的远近按概率随机置为 ±1。

8349dd0bd7b11ac9bb8814edf2c5b6b92d8e8b47

公式中是一个 clip 函数:

ab4076fdb0eaaead8f09837162f1d256e4c4859e

第二种二值化方式看起来更为合理,但是由于引入了按概率分布的随机一比特数,所以硬件实现会消耗很多时间,我们通常使用第一种量化方法来对权重和输出进行量化。

虽然 BNN 的参数和各层的输出是二值化的,但梯度不得不用较高精度的实数而不是二值进行存储。因为梯度很小,所以使用无法使用低精度来正确表达梯度,同时梯度是有高斯白噪声的,累加梯度才能抵消噪声。

另一方面,二值化相当于给权重和输出值添加了噪声,而这样的噪声具有正则化作用,可以防止模型过拟合。所以,二值化也可以被看做是 Dropout 的一种变形,Dropout 是将输出按概率置 0,从而造成一定的稀疏性,而二值化将权重也进行了稀疏,所以更加能够防止过拟合。

由于 sign 函数的导数在非零处都是 0,所以,在梯度回传时使用 tanh 来代替 sign 进行求导。假设 loss function 是 C,input 是 r,对 r 做二值化有:

cd35f0c957bc0d05bf3c0b6bdf14203a863712ea

C 对 q 的的导数使用 gq 表示,那么 q 对 r 的导数就变成了:

b21046e5e33b5621533d50ea0b20122b45807eb0

这样就可以进行梯度回传,给出一种包含 bn 的二值化网络的梯度算法:

8a1c7fb0e4b15684df5ba01161d96a4dfd0728e9

BN 最大的作用就是加速学习,减少权重尺度影响,带来一定量的正则化,可以提高网络性能,但是,BN 涉及很多矩阵运算(matrix multiplication),会降低运算速度,因此,提出了一种 shift-based Batch Normalization。

使用 SBN 来替换传统的 BN,SBN 最大的优势就是几乎不需要进行矩阵运算,而且还不会对性能带来损失。基于 SBN,又提出 Shift based AdaMax:

299e6c15272081fd832f077ed7049a9ad1650841

网络除了输入以外,全部都是二值化的,所以需要对第一层进行处理:

75a6365d307be337d652c53975e84e7693e2130a

作者还对二值化网络扩展到 n-bit quantized:

5d9a9368ee54957c5779df996eb4fe095e7719fd

二值化的论文对 mnist、cifar-10、SVHN 进行了测试,最后得到的 test error 如下:

1fd7caabac1c749850ecba1fa879d71b0571466f

完了作者为了挑战高难度,又用了 alexnet 和 googlenet 在 imagenet 上做了测试,看出来结果也是一般,所以较复杂的网络较大的数据集采用 bnn 看来影响还是蛮大的。

作者不服气又提出了一些小技巧,比如什么放宽 tanh 的边界啊,用 2-bit 的 activitions,也提升了一些准确率,作者也在 rnn 做 language task 上进行了二值化,结果也贴出来,分析了那么多模型,应该可以说在牺牲那么多运算和储存资源的情况下准确率差强人意。

a1ab318e4ae6a22d94e88c17220633286261c790


x = tf.placeholder(tf.float32, shape=[batch_size, 28, 28, 1])
net = tl.layers.InputLayer(x, name='input')
net = tl.layers.BinaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')

net = tl.layers.SignLayer(net)
net = tl.layers.BinaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)

net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')

net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

上面是给 MNIST 设计的一个 BinaryNet。

作者最后又分析了一下时间复杂度和功率效率,毕竟 bnn 的主要任务就是压缩和加速,说了时间复杂度可以降低 60%,原理是说可以卷积核复用。

举个例子,因为一个 3 x 3 的卷积核做了二值以后,只有 2 的 9 次方个独一的卷积核,相比于没有二值化的卷积核,在文章中的 cifar-10 网络中独一的卷积核数量只有 42% 那么多。

内存资源减少了 31/32(原本每个参数 32bit,压缩后每个参数 1bit),运算资源,硬件层面上看 32bits 损耗 200 个位,1bit 只损耗一个位(bit-wise operation)。

最后在 gpu 上还可以进行 SWAR(single instruction,multiple data within register)的处理,对 xnor 进行优化,SWAR 的基本思想是将 32 个二进制变量组连接成 32 位寄存器,从而在按位操作(例如 XNOR)上获得 32 倍的加速。

使用 SWAR,可以仅用 3 条指令评估 32 个连接:

c01eaa20aba2e2ee5317afcf5e14c14fa25b2790

就可以用 1(加和)+4(popcount,四个 8 位)+1(xnor)个 time cycle 来进行运算,原来的d6879a642f43df7cec2b1768782b2cf19d4ee693,则是 32 个 time cycle,提高了 32/6 倍的速度。

82bac104c9802ed550f03223d218182345f30505

Xnor-Net 在 BNN 的基础上引入了比例因子,让二值化之后的参数和原始的参数的 L2 范数最小,提高了模型的精度。

7cb76e1c9d0b1c5c58914ef091bced87e9d6a128

对卷积操作的比例因子进行简化,降低了其运算复杂度。

b74aeb1d748b4449a0728d68c7802b984deb3e7c

由于在一般网络下,一层卷积的 kernel 规格是固定的,kernel 和 input 在进行卷积的时候,input 会有重叠的地方,所以在进行量化因子的运算时,先对 input 全部在 channel 维求平均,得到的矩阵 A,再和一个 w x h 的卷积核 k 进行卷积得到比例因子矩阵 K,其中:

a4ff8005584616e0c98954c14b9aaf31a8366ada

在 imagenet 上结果也比 bnn 要好很多。

d6b8d695b91afe1d60f86d7d6d13c5320334efb8

Ternary-Net

d47e62d2b349aca45e42305ed6714efbe5ed61d9Ternary Weight Networks paper

权值三值化的核心:

首先,认为多权值相对比于二值化具有更好的网络泛化能力。其次,认为权值的分布接近于一个正态分布和一个均匀分布的组合。最后,使用一个 scale 参数去最小化三值化前的权值和三值化之后的权值的 L2 距离。

基本原理阐述如下:

参数三值化的方式如下:

b3681492b45e17ad4a84b1955c939fe48e370a04

其实就是简单的选取一个阈值(Δ),大于这个阈值的权值变成 1,小于-阈值的权值变成 -1,其他变成 0。当然这个阈值其实是根据权值的分布的先验知识算出来的。本文最核心的部分其实就是阈值和 scale 参数 alpha 的推导过程

在参数三值化之后,作者使用了一个 scale 参数去让三值化之后的参数更接近于三值化之前的参数。具体的描述如下:

5f09a52f56dae1fcd51430fa80b07dac68901d68

利用此公式推导出 alpha 的值如下:

94931934c1249339d5d30d1eaab98564180bd69e

由此推得阈值的计算公式如下:

859164ee2ef46dc353aea798c7c1c78bc7a9de61

由于这个式子需要迭代才能得到解,会造成训练速度过慢的问题,所以如果可以提前预测权值的分布,就可以通过权值分布大大减少阈值计算的计算量。文中推导了正态分布和平均分布两种情况,并按照权值分布是正态分布和平均分布组合的先验知识提出了计算阈值的经验公式。

2579ff48b49044b43c134c92a30f7657707898eb

三值化论文的最终结果如下:

354315663fecdb7f93891113935e5a062d2f0f90

反正就是抓住 BNN 一顿 diss 呗,谁让人家准确率高呢。

当然,这种方法有进化版本,我们完全可以将权值组合变成(-2,-1,0,1,2)的组合,以期获得更高的准确率。正好我之前也推过相关的公式,现在贴出来供大家参考,这个时候权值的离散化公式变成了:

5d60dceadac1e9e823a657dc3fe472777d3dc44b

Scale 参数的计算公式变成了:

c5f9e42ea33c004a17a649a95432036667da8598

此时阈值的计算公式变成了:

0bcd0e516586bb92867cc9dc18bd6371d400c772

需要声明的是,这个算法我只在一个非常不知名的 matlab 的一个纯 cpu 版本慢到爆炸反正就是难以忍受那种框架上面实际实现过,取得了比三值化更高的准确率,但是!对于这个算法在 tensorflow 上面的实现我真是一筹莫展,因为 tensorflow 某些机制……算法的具体实现方式如下:

7187f41700eda52c0ec55fc7fd068b5c8959e6fc


net = tl.layers.InputLayer(x, name='input')
net = tl.layers.TernaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.TernaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.TernaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.TernaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')
return net

上面是 TensorLayer 提供的三值化的 MNIST 测试代码。

权值三值化并没有完全消除乘法器,在实际前向运算的时候,它需要给每一个输出乘以一个 scale 参数,然后这个时候的权值是(-1,0,1),以此来减少了乘法器的数目,至于为什么减少跟 BNN 是一样的道理。

DoReFa-Net

d47e62d2b349aca45e42305ed6714efbe5ed61d9DoReFa-Net: Training Low Bitwidth Convolutional Neural Networks with Low Bitwidth Gradients

Face++ 团队在 16 年 6 月提出的 Dorefa-Net 和上面两种量化方法思路也是比较接近,但 DoReLa-Net 对比例因子的设计更为简单,这里并没有针对卷积层输出的每一个过滤映射计算比例因子,而是对卷积层的整体输出计算一个均值常量作为比例因子。这样的做法可以简化反向运算,因为在他们反向计算时也要实现量化。

文章首先概述如何利用 DoReFa-Net 中的比特卷积内核,然后详细说明量化权值,激活和梯度以低比特数的方法。

和之前 BNN 的点积方法一样,DoReFa 也采用了这种简化的点积方式。

227f6098b729599014c5caf9d4c354d84c194460

对于定点数 x 和 y,可以得到下面的公式:

ac14ae0e7786284ae00284b40ea6c88284356556

同样为了规避 0 梯度的问题,采用了直通估计(STE):

af9616d2e7c279debee666b0f12c2e2b715ea17c

对于权重二值化的梯度回传,采用下面的方法,即二值化乘比例因子,回传时直接跳过二值化。

dc91fe4014124c9ee6963311740334c02a33be67

比特数 k 大于 1 的梯度回传,需要先对参数 clip 到 [0,1] 之间:

8137a320391efa80ac7a4b5383223bef2c12635f

由于二值化输出会降准确率,所以采用 k-bit 量化(k>1),这里的 r 也要经过 clip。

b2ed0230071657c81f0fd4c9cbd11469410fb8d7

DoReFa 的梯度量化方法比较复杂,因为梯度是无界的,并且可能具有比隐层输出更大的值范围。我们可以通过使可微分非线性函数传递值来将隐层输出范围映射到 [0,1]。 但是,这种构造不适用于渐变。 文章设计了以下用于梯度 k 位量化的函数,这里 dr 是 r 对损失函数 C 的偏导。

fcd5fd385d25edc572622c0407e3258bea6b1aaf

为了补偿量化梯度带来的潜在偏差,在 clip 后的结果增加了一个高斯噪声。

2b090a1515f4b83072ea594ffc4eb820219144a3

梯度的量化仅在回程中完成,因此文章在每个卷积层的输出上应用以下 STE:

bd308e161a0a94c25de9bfa663df3fe0a2f208fa

最终得到了 DoReFa-net 的算法,这里对第一层和最后一层不做量化,因为输入层就图像任务来说通常是 8-bit 的数据,做低比特量化会对精度造成很大的影响,输出层一般是一些 one-hot 向量,所以一般对输出层也保持原样,除非做特殊的声明。

DoReFa-net 为了进一步节省资源将 3,4,6 步放在一起做,将 11,12 步融合在一起,节省了中间步骤的全精度数储存消耗的资源。

c9028197f6935b84dfa4ab85fdc852a03933ddbe

DoReFa-Net 分别对 SVHN 和 ImageNet 进行了实验,准确率如下:

4714813b9a98898e7252c753ef39a1c741793fd9


net = tl.layers.InputLayer(x, name='input')
net = tl.layers.DorefaConv2d(net, 1, 3, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1') #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.DorefaConv2d(net, 1, 3, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2') #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.DorefaDenseLayer(net, 1, 3, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.DenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

上面是 TensorLayer 提供的 DoReFa-Net 的 MNIST测试代码,需要注意的是不同于DoReFa-Net,我们的实现默认梯度为 32bits 来尽量获得更高的训练准确率,而且在实际的硬件前向配置中其实是不需要梯度信息的。

压缩算法局限性

目前的压缩算法是存在一些局限性的,最主要的问题还是准确率,论文中为了数据好看往往是选择传统的神经网络结构比如 AlexNet,VGG 作为测试对象,而这种网络一般是比较冗余的。

如果想把参数压缩方案和其他一些方案结合,比如说下面讲到的一些 SqueezeNet,MobileNets,ShuffleNet 结合起来,会对准确率造成比较大的影响。原因可以归为参数压缩算法其实是一个找次优解的问题,当网络冗余度越小,解越不好找。所以,目前的高精度压缩算法只适合于传统的有很多冗余的网络

更多加速方法

理论上来讲,量化模型是通往高速神经网络最佳的方法,不过由于种种问题,如实现难度大、准确性不稳定,使用门槛非常大,所以除了量化模型外,目前有很多更加常用的模型加速方法:

d47e62d2b349aca45e42305ed6714efbe5ed61d9A Survey of Model Compression and Acceleration for Deep Neural Networks (end of 2017)

这是 2017 年底的一篇 survey。

有基于 Pruning 的:

d47e62d2b349aca45e42305ed6714efbe5ed61d9Channel Pruning for Accelerating Very Deep Neural Networks

也有基于改变卷积方式的,这是目前最常用的方法:

d47e62d2b349aca45e42305ed6714efbe5ed61d9SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size

d47e62d2b349aca45e42305ed6714efbe5ed61d9MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

d47e62d2b349aca45e42305ed6714efbe5ed61d9ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices

传送门:纵览轻量化卷积神经网络:SqueezeNet、MobileNet、ShuffleNet、Xception

值得注意的是,当 TensorLayer 和 Keras 使用完全相同的 MobileNet 时,TensorLayer 的速度是后者的 3 倍(Titan XP 上测试),大家可以试试。

关于AI芯片

关于硬件实现,这里要推荐一篇非常好的survey:

http://www.rle.mit.edu/eems/wp-content/uploads/2017/11/2017_pieee_dnn.pdf

大家看完这篇文章会对目前最先进的神经网络硬件加速架构有所了解。

由于目前基于 PC 平台的神经网络加速一定程度上不能满足需要,开发基于硬件例如 FPGA 的硬件加速平台显得很有必要。其实硬件加速神经网络前向运算的最主要的任务就是完成卷积优化,减少卷积运算的资源和能源消耗非常核心

卷积优化的主要思路

内存换取时间:如果深度学习中每一层的卷积都是针对同一张图片,那么所有的卷积核可以一起对这张图片进行卷积运算,然后再分别存储到不同的位置,这就可以增加内存的使用率,一次加载图片,产生多次的数据,而不需要多次访问图片,这就是用内存来换时间。

乘法优化:以下图为例,上面是两张图片,右边是卷积核。我们可以把卷积核心展开成一条行,然后多个卷积核就可以排列成多行,再把图像也用类似的方法展开,就可以把一个卷积问题转换成乘法问题。这样就是一行乘以一列,就是一个结果了。这样虽然多做了一些展开的操作,但是对于计算来讲,速度会提升很多。

270bd4ad6540a5e47d55730d35b490bb1f79938d

GPU优化:

1. 了解 IO 访问的情况以及 IO 的性能;

2. 多线程的并行计算特性;

3. IO 和并行计算间的计算时间重叠。

对于 NVIDIA 的 GPU 来讲,内存访问是有一些特性的,连续合并访问可以很好地利用硬件的带宽。你可以看到,NVIDIA 最新架构的 GPU,其核心数目可能并没有明显增加,架构似乎也没有太大变化,但在几个计算流处理器中间增加缓存,就提高了很大的性能,为 IO 访问这块儿带来了很大优化。

Strassen 算法

分析 CNN 的线性代数特性,增加加法减少乘法,这样降低了卷积运算的计算的复杂度d905e7a3a2fef9d7f38d0f7838aeab625e18c9fd,但是这种方法不适合在硬件里面使用,这里就不做详细的介绍了。

卷积中的数据重用

在软件中的卷积运算,其实我们是在不断的读取数据,进行数据计算。也就是说卷积操作中数据的存取其实是一个很大的浪费,卷积操作中数据的重用如下图所示:

ec843e9b0c45fab7885cb3811d1180300c35185b

那么想办法减少数据的重用,减少数据的存取成为解决卷积计算问题的一个很重要的方面。

目前这样的方法有很多种,最主要的方法包括以下几种:

权重固定:最小化权重读取的消耗,最大化卷积和卷积核权重的重复使用;

输出固定:最小化部分和 R/W 能量消耗,最大化本地积累;

NLR (No Local Reuse):使用大型全局缓冲区共享存储,减少 DRAM 访问能耗;

RS:在内部的寄存器中最大化重用和累加,针对整体能源效率进行优化,而不是只针对某种数据类型。

下表是在 45NM CMOS 的基础上对于不同的操作的能耗进行的统计。对 32 位的各种操作的能耗进行统计,可以看到从 DRAM 里面存取数据的能量消耗是最大的。是 32 位整型数据进行加法的能量消耗的 6400 倍。那么,从数据存取角度考虑卷积的优化就显得尤为必要了。

11530f5eb6d20c7c03ddf775340939dd9ed5c22e

可行性分析

在进行设计之前先对设计的可行性进行分析,分析过程包括卷积运算可实现性分析、卷积运算并行性分析,卷积的计算公式可以表示成下面的形式:

2b9d515bbd3613b1445a135be23faddedec645a3

各个参数的意义在表内详细表示:

6b949fb5b93ac87cbd8a26305a52aa6cb9ef6b0a

在 GPU 中加速时,主要通过将数据最大程度的并行运算,增加了 GPU 的使用率从而加快了速度。但是这种方法在硬件实现的时候是不可行的,因为这种方法本质上没有降低能耗,而 DNN 模型的高能耗和大量的数据是其在可穿戴设备上面进行部署所需要面对的困难。

下面对一个卷积部分和运算进行分析,如下图 :

对第一组的 PE 整列,输入的是从 Image 的第 0 行到第 R-1 行的 S 列的数据,同样的对于第二列的 PE 阵列输入的是第 2 行到第 R 的 S 列的数据。每一列的 PE 计算得到一个最终的 Psum 的结果,那么如果设置 PE 阵列的列数为 N 的话,每次我们就可以计算得到连续的 N 个部分和的结果。

不断更新 PE(process element,即处理单元)中 Image 缓冲区的数据,就可以模拟卷积在水平方向上面的滑动,不断更新整个 PE 阵列的数据输入,就可以模拟卷积窗在垂直方向上面的滑动,最终完成整个卷积运算的实现。

对应的卷积运算公式的细节在图中已经给出了,每一组 PE 产生一个部分和的结果的话,那么增加 PE 阵列的组数,就可以一次性产生多个部分和计算结果,这里的组数就是并行度。

上面的内容简单论证用数据重用的方式实现卷积运算的可行性,至于实现的具体数据流,还有相对用的系统的架构。

bf6a66601392be2ba83ef53c02bfda4ba8b5d136

压缩算法在实际硬件芯片的应用

其实压缩算法应用硬件芯片非常简单,就是简单的将硬件芯片原来使用的乘法器进行替换,如果是 BNN,参数只有两种情形,那么如果参数为 1 的时候,直接通过,不计算,如果参数为 -1 的时候,翻转最高位即可。

同理三值化中增加了一个 0 参数,这个可以直接跳过不进行计算。至于参数为(-2,-1,0,1,2)的情形,参数为 2 时就增加了一个移位运算,参数为 -2 的时候增加了一个最高位的翻转。

如果是 DoReFaNet,权值和输出都固定在一定的种类内部,那么他们的乘积情形也只有一定的种类,这个时候相当于把乘法器变成了一个寻址操作,每次乘法只需要在 LUT(look-up table,查找表)里面寻找到正确的结果读出即可。


原文发布时间为:2018-06-1

本文作者:郝泽宇

本文来自云栖社区合作伙伴“PaperWeekly”,了解相关信息可以关注“PaperWeekly”。

网友评论

登录后评论
0/500
评论
技术小能手
+ 关注
所属云栖号: paperweekly