雷锋网按:本文作者痴笑,矽说(微信号:silicon_talks)主笔。本文为《脑芯编:窥脑究竟,织网造芯》系列第四篇。
写这篇的时候想到哥哥的《我》,因为这次的主角就是,那个特里独行的我:
我就是我,是长度不一样的开拓
天空海阔,要讨最并行的生活
我喜欢我,让矢量算出一种结果
简单的指令集,一样加速的很妥妥
他的名字,叫做
SIMD
Single Instruction Multiple Data
话接上回《窥脑究竟,结网造芯(三)》我们说到,有三种方式,可以在传统体系结构的基础上面向神经网络人工智能硬件进行优化设计。这次,我们先来提第一种——在简单指令集(RISC)中增加指令的方式来达到性能的优化。我们将着重介绍如何加速卷积核的计算(忘了(一)和(二)?点这里!)
这次的故事,要从并行计算机体系结构讲起。说到并行计算机体系结构,就要掉一个书袋——
我从上的第一门计算机体系结构课,到并行提算计体系机构,到高级计算机体系结构都在用这一本书。不得不感谢作者让我少买了好多教科书钱。当然,牛x书的作者也很牛x,这里就不八卦了。有人愿意把这本书叫做计算机体系结构的bible,我不评论,但是下面我们所讲的,好多都出自这本书。
言归正传,这个特立独行的故事从这里开先,我们要认识一个老爷爷(还活着好像),他叫Michael Flynn。老人家生于大萧条时代的扭腰城,一不小心提出了一个分类法,叫做Flynn Taxonomy(1966)。然后计算机体系机构就被Flynn taxonomy的五指山给压几十年。
Flynn Taxonomy的五指山把计算机结构分为两个部分:指令与数据。在时间轴上指令与数据可以单步执行或多次运行进行分类,即单指令单数据(SISD),单指令多数据(SIMD),多指令单数据(MISD)和多指令多数据(MIMD)。
并行,从Pipeline到SIMD
Flynn taxonomy给并行计算机体系结构指了两条明路——指令级并行和数据级并行。
首先来看下指令与数据的关系。指令是处理器单步可以实现的操作的集合。指令集里的每一条指令,都包含两个部分:(1)什么操作;(2)对什么数据进行操作。专业地,我们把前者叫做opcode,后者叫做operand(也就是数据)。当然,并不是所有的命令都有数据操作。传统定义的指令集里面,对应的operand不超过2。比如,加减乘除都是典型的二元操作。数据读写就是一元的,还有些就是没有任何数据的操作,比如一个条件判断(if)发生了,根据判断结果程序何去何从,只是一个jump操作,他并不需要任何数据输入。
指令级并行的第一种、也是最经典的办法叫做时间上并行,这是所有的体系结构教科书最喜欢教的流水线架构(pipeline)。简而言之,在发明流水线以前,处理器里面只有一个老司机,什么事情都得他来干,但是下一条指令得等老司机干完上一个~但是,流水线就是把一个老死机变成了三个臭皮匠,每个人干三分之一就给下一个,这样下一条指令只需在上一条被干完1/3后就可以进来了。虽然老司机体力好,但赶不上年轻的臭皮匠干的快啊。这样,流水线可以实现时间上的指令集并行,成倍提高实际指令的处理效率。
经典MIPS-5 级流水线
有一就有二,有时间当然就有空间。所以,第二种办法是空间上的并行。空间并行基于一个观察——对数据的操作有很多种——加减乘除移位、整数操作、浮点操作……每一个模块的处理(ALU/EXU)是独立的,所以,就空间上,一个浮点加法在处理的时候,完全可以同时进行一个整数移位操作,像老顽童教小龙女的左右互搏分心二用。所以,在计算机体系的历史上,练成“左右互博”术的处理器包括超标量(Superscalar)/超长指令(Very Long Instruction Word, VLIW)处理器。具体我们先不展开。
相比于流水线/超标量复杂的修炼过程(黄蓉都练不会“左右互博”),数据级并行就是简单纯粹的叠加硬件,打造并行处理的“千手观音”:
“千手观音”的学名叫做SIMD,Single Instruction Multiple Data,单指令多数据(流)处理器。其实,说白了就是原来有处理单元(ALU/EXU)现在一个加法器,现在变成了N个了。对应神经网络的计算,原来要M次展开的乘累加,现在只要M/N次,对应的时钟和时间都显著地降低。
简单粗暴的并行,不仅提高了让每个指令的数据吞吐率,还让本身单一的标量处理进化成阵列式的“矢量型”处理,于是就有SIMD又有了“矢量处理”指令的称呼。其实,SIMD并不是到了神经网络再兴起的新玩样儿,早在MP3的年代,SIMD处理器就广泛地使用在各类信号处理芯片中。所以关于SIMD指令也早有了需要行业标准。以下,我们就来看一个SIMD指令集实例——
ARM NEON,厉害了word令
在上一编中,我们简单提到了史上第一个攻城掠地的RISC-ARM。为手机、平板等便携式的最重要处理器,ARM的SIMD指令也是王者风范,从它的名字开始——NEON。
NEON的指令的操作的输入(operand)是一组128位位宽的寄存器,但这个寄存器存着的几个数,就由码农自己去预定义了,可以是4个32位的浮点,或者定点,或者8个16位定点,或者16个8位定点……整个指令集宽泛地定义了输入、输出的位宽,供变成者自由支配,考虑到在神经网络中,前馈网络往往只要16位、8位整数位宽,所以最高效的NEON命令可以一次实现16个乘累加计算(16个Synapse)。
仅仅是SIMD怎能彰显NEON的侠者风范? NEON还充分应用了指令级并行,采用10级流水线(4级decode+6级运算单元),可以简单地理解为把卷积计算的吞吐率由提高了10倍。加起来,相比与传统的单指令5级流水,提高至少32倍的效率。再辅以ARM Cortex A7以上的超标量核心处理单元,筑起了第一条通用并行计算的快车道。
当神经网络遇上SIMD,滑起吧!
流水线和SIMD都是在神经网络还没羽翼丰满的时候就已经称霸江湖的大侠。在神经网络不可一世的今天,这两者还是固步自封么?答案显然是否定的。
当通用SIMD处理器遇上神经网络,他们既碰撞出了火花,也开始相互埋怨。我们先说埋怨——存储空间管理。我们知道,在NN中通常每个卷积核都需要先load系数与输入数据,再算出部分的乘累加结果,再store回存储空间。而指令执行与存储空间的通信就是我们上一编讲到的——冯诺伊曼瓶颈。对于神经网络来说,如此多次的存储读写是制约性能的关键。减少数据的载入与中间结果是面向神经网络的SIMD指令的主要问题。
那火花是什么?
这就给SIMD带来了一个面向神经网络的新机遇——部分更新与数据滑行(sliding)。我们来看下面这张动图「原作为MIT Eyeriss项目研究组」。
对于一个采用SIMD的卷积核,有一组输入是固定——系数矢量,而另一组输入像一个FIFO,在起始填满后,每次注入一个单元(也排出一个单元)进行乘雷佳,另外上一次累加的结果在保存在执行单元的寄存器内,只有最终的卷积核结果会写回到存储器中。
这样,在神经网络中,无论是数据导入、还是结果输出,起对存储空间的访问都会大大降低。当然,上述示意图仅仅是一维的。当卷积核的维度达到二三维时,情况会复杂很多。这里推荐大家可以去读读MIT的Eyeriss,Kaist的MIMD,或者IMEC的2D-SIMD(ENVISION)。这里就不太多展开了。
好了,这次就到这里。所谓“烛台簇华照单影”就是那一粒粒自由定义的小数据,在同一声SIMD的指令下,排成队,集成行,成为了一个孤独的矢量运算。
特别鸣谢复旦大学脑芯片研究中心提供技术咨询,欢迎有志青年报考。