汇编版本的neon代码编写与维护成本高,但速度比c版本更快。秉着精益求精的态度,我们实现了汇编代码:
void vector_product_neon(const char * start_a, const char * start_b, int & result, const int cnt) { int res = 0; asm volatile( "vmov.s32 q2, #0" "\n\t" "lsr %[cnt], %[cnt], #4" "\n\t" ".charloop:" "vld1.s8 {d0}, [%[vec1]]!" "\n\t" "vld1.s8 {d1}, [%[vec2]]!" "\n\t" "vmull.s8 q1, d0, d1" "\n\t" "vld1.s8 {d0}, [%[vec1]]!" "\n\t" "vld1.s8 {d1}, [%[vec2]]!" "\n\t" "vmlal.s8 q1, d0, d1" "\n\t" "vaddw.s16 q2, q2, d2" "\n\t" "vaddw.s16 q2, q2, d3" "\n\t" "subs %[cnt], %[cnt], #1" "\n\t" "bne .charloop" "\n\t" "vadd.s32 d4, d4, d5" "\n\t" "vmov.s32 r4, d4[0]" "\n\t" "add %[sum], r4" "\n\t" "vmov.s32 r4, d4[1]" "\n\t" "add %[sum], r4" "\n\t" : [sum]"+r"(res) : [vec1]"r"(start_a), [vec2]"r"(start_b), [cnt]"r"(cnt) : "r4", "cc", "memory" ); result = res; }2.奇异值分解优化声学模型运算量
为了降低乘加运算的次数,我们决定利用奇异值分解来对DNN进行重构,通过裁剪掉最小的奇异值及其相对应的特征向量,来达到减少乘加运算数量的目标。奇异值分解将任意矩阵Wm×n(不失一般性,假设m≤n)分解成3个矩阵相乘:Wm×n =Um×mΣm×mVm×n。
其中:Σm×m 为对角矩阵,即Σm×m =diag(σ1,σ2,…,σm),它的对角元素即为Wm×n的奇异值;Um×m 为单位正交矩阵,其列向量为与奇异值对应的特征向量;Vm×n中的行向量是互相单位正交的,也是与奇异值对应的特征向量。
下图是我们以DNN模型其中一层网络作为例子,阐述我们在重构DNN中的模型转化,其中原始DNN模型为图中上方子图(a),新重构DNN模型在下方子图(b)所示:
a:原始DNN模型的一层结构
(b)新DNN模型的两层对应结构
利用SVD对声学模型计算量优化大致分为3个步骤
(1)训练初始DNN神经网络;
(2)对权重矩阵进行奇异值分解;
(3)对重构后的DNN模型重新训练。
通过基于SVD的模型压缩方法,我们可以在稍微降低模型性能的前提下,将声学模型计算量减少30%。
3.哈夫曼优化语言模型内存
一般地,n-gram语言模型可以用一张有向图存储便于介绍存储空间以及快速查询,这张图上的边要存储词汇信息。我们知道以汉语为例,不同词语的出现频率相差极大,如果所有词汇的label id都用int类型存储,那空间的利用率较为低下。
以“我”“要”“吃饭”为例,假设语言模型的词汇频率:我>要>吃饭,那么我们可以构建图3的哈夫曼树,则四个字使用的编号码分别为:我(0),要(10),吃饭(110)
二叉哈夫曼
十六叉哈夫曼树
然而,采用图4的二叉树数据结构,一次只能处理1bit效率较低,也不便于工程实现。所以在工程实现的时候,我们按4bits编码为单位,对词汇进行分类存储处理。
我们使用一棵16叉树的哈夫曼树结构,每层树节点的编号总量是上一层的16倍。树中的所有编号为0的子节点用于储存词汇,越高频的词汇储存于深度越低的节点位置。
通过哈夫曼优化,我们的引擎最终成功降低了25%的内存占用,同时引擎是资源文件也得到50%左右的优化。
04
识别性能的优化
1.基于TDNN优化声学模型
近几年,TDNN(Time-Delay Neural Network,延时神经网络)【5】的拓扑结构被应用于语音识别。事实上,该结构于1989年被提出,随着近几年技术的发展,重新进入了大家的视线。
DNN结构
DNN的拓扑网络仅针对单一特征时刻点建模。
TDNN结构