近些年,CPU等通用处理器的性能提升速度放缓,为了继续满足各行各业对高能效计算日益增长的需求,以FPGA,GPU,DSP,NPU等为代表的异构算例,一夜之间在众多的新型热点领域受到广泛关注。
首先结合市面上能够看到的具体的产品来展示这些异构核之间的不同。
CPU、GPU 都属于冯·诺依曼结构,指令译码执行、共享内存。冯氏结构中,由于执行单元(如 CPU 核)可能执行任意指令,就需要有指令存储器、译码器、各种指令的运算器、分支跳转处理逻辑。由于指令流的控制逻辑复杂,不可能有太多条独立的指令流,因此 GPU 使用 SIMD(单指令流多数据流)来让多个执行单元以同样的步调处理不同的数据,CPU 也支持 SIMD 指令,但SIMD指令的支持只能算是CPU的一个锦上添花的功能,不构成CPU的主要功能,比如X86的SSE,MIPS/RISCV的向量指令,以及ARM 的NEON,都是CPU的可配置功能。
FPGA 之所以比 CPU 甚至 GPU 能效高,本质上是无指令、无需共享内存的体系结构带来的福利, FPGA 每个逻辑单元的功能在重编程(烧写)时就已经确定,不需要指令。
谷歌发布的人工智能芯片:Tensor Processing Unit(TPU),这是ASIC,TPU兼具了CPU与ASIC的特点,可编程,高效率,低能耗,还有多种多样的各类用于CNN深度学习的NPU,它们也都可以归为ASIC的一种。
DSP有些类似于CPU,但是它拥有更强的并行计算能力,支持多发射,VLIW指令以及单精度,双精度浮点运算和专门用于SIMD加速的MAC阵列等,使其能耗比远高于CPU。
GPU的峰值性能要高于FPGA,PPA,性能功耗比也要更优一些,FPGA基本单元的计算能力有限。为了实现可重构特性,FPGA 内部有大量极细粒度的基本单元,但是每个单元的计算能力(主要依靠LUT 查找表)都远远低于CPU 和GPU 中的ALU模块,单位面积的功耗高,算力弱于GPU,价格高,PPA没有优势。
从架构上讲影响CPU并行计算原因在哪里?CPU作为通用处理器,兼顾计算和控制,70%晶体管用来构建Cache 还有一部分控制单元,用来处理复杂逻辑和提高指令的执行效率,所以导致计算通用性强,可以处理计算复杂度高,但计算性能一般。CPU的指令执行过程是:取指 ->译码 ->执行->访存,只有在指令执行的时候,计算单元才发挥作用,这样取指令和指令译码的两段时间,计算单元是不在工作的,为了解决这个问题,人们设计了流水线来解决指令微操作之间的并行。另外,CPU程序的指令间相关性打,但也不是没条指令都相关,所以又设计了超标量流水线来解决指令级并发的问题,但是这种程度的指令级并发和GPU相比,仍然是小巫见大巫,即便后面又设计出多核来扩充指令并行度,也是杯水车薪,这是有被处理事物的本质特点所决定的,CPU的设计基因使它不具备大规模扩展并行度的能力。
GPU同CPU一样也是指令执行过程:取指 ->译码 ->指令,只有在指令执行的时候,计算单元才发挥作用。GPU的逻辑控制单元相比CPU简单,所以要想做到指令流水处理,提高指令执行效率,必然要求处理的算法本身复杂度低,处理的数据之间相互独立,所以算法本身的串行处理会导致GPU浮点计算能力的显著降低。
FPGA的强项是能够实现可重构计算, 一个时钟周期内就可以做很多的事,完成非常复杂的功能,而其它品类多个时钟做一件事,有多有少,但都不如FPGA,FPGA的硬件软件化非常灵活,可以构成可扩展的计算平台。和GPU相比,FPGA 同时拥有流水线并行和数据并行,而 GPU 几乎只有数据并行(流水线深度受限)所以latency非常小。例如处理一个数据包有 10 个步骤,FPGA 可以搭建一个 10 级流水线,流水线的不同级在处理不同的数据包,每个数据包流经 10 级之后处理完成。每处理完成一个数据包,就能马上输出。而 GPU 的数据并行方法是做 10 个计算单元,每个计算单元也在处理不同的数据包,然而所有的计算单元必须按照统一的步调,做相同的事情(SIMD,Single Instruction Multiple Data)。这就要求 10 个数据包必须一起输入、一起输出,输入输出的延迟增加了。当任务是逐个而非成批到达的时候,流水线并行比数据并行可实现更低的延迟。因此对流式计算的任务,FPGA 比 GPU 天生有延迟方面的优势。
总体上看,各类异构芯片品类各有优劣,短时间内谁也替代不了谁,处于共存竞争的局面。
下面从软件角度,分析几类适用于异构芯片的并行处理方案。
CUDA是一种新的操作GPU计算的硬件和软件架构,它将GPU视作一个数据并行计算设备,而且无需把这些计算映射到图形API,GPU 编程早期被称为 通用GPU编程(General-popuse GPU programing,GPGPU),这一时期的程序员借助 Direct3D 和 OpenGL 的图形 API 通过迷惑图形硬件来执行非图形的计算任务。CUDA (自2007年)的出现改变了这一切。
CUDA 软件栈(software stack)包含多个层,如下图所示,设备驱动程序(device driver)、应用程序编 程接口(API)及其运行时、两个较高级别的通用数学库,即 CUFFT 和 CUBLAS,CUDANN。
CUDANN广泛应用在深度学习框架中,在此类场景中,软件栈如下图所示:
CUDA在软件方面组成有:一个CUDA库、一个应用程序编程接口(API)及其运行库(Runtime)、几个较高级别的通用数学库,即CUFFT,CUDANN和CUBLAS。CUDA改进了DRAM的读写灵活性,使得GPU与CPU的机制相吻合。另一方面,CUDA提供了片上(on-chip)共享内存,使得线程之间可以共享数据。应用程序可以利用共享内存来减少DRAM的数据传送,更少的依赖DRAM的内存带宽。
CUDA程序构架分为两部分:Host和Device。一般而言,Host指的是CPU,Device指的是GPU。在CUDA程序构架中,主程序还是由CPU 来执行,而当遇到数据并行处理的部分,CUDA 就会将程序编译成 GPU能执行的程序,并传送到GPU。而这个程序在CUDA里称做核(kernel)。CUDA允许程序员定义称为核的C语言函数,从而扩展了C语言,在调用此类函数时,它将由N个不同的CUDA线程并行执行N次,这与普通的C语言函数只执行一次的方式不同。执行核的每个线程都会被分配一个独特的线程ID,可通过内置的threadIdx变量在内核中访问此ID。在 CUDA 程序中,主程序在调用任何 GPU内核之前,必须对核进行执行配置,即确定线程块数和每个线程块中的线程数以及共享内存大小。
关于CUDA的编程例子,可以参考这篇博客:
Darknet环境安装CUDANN实现推理加速_tugouxp的专栏-CSDN博客首先参考下面几篇文章安装darknet,cuda的基础环境:Yolov3网络的物体检测_tugouxp的专栏-CSDN博客1.Get darknet 代码$ git clone https://github.com/pjreddie/darknet$ cd darknet$ makecaozilong@caozilong-Vostro-3268:~/yolo$ git clone https://github.com/pjreddie/darknet正克隆到 'darknet'...remote: Enhttps://blog.csdn.net/tugouxp/article/details/121306727
采用这种方式进行计算加速的项目有比如darknet,在darknet/Makefile中通过开启GPU=1,CUDNN=1来进行计算加速。
OpenMP是用来对CPU计算进行加速的机制,penMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。
一个典型的fork-join执行模型的示意图如下:
OpenMP编程模型以线程为基础,通过编译Directive指令指导并行化,有三种编程要素可以实现并行化控制,他们分别是编译directive、API函数集和环境变量。
OpenMP具体使用可以参考这篇文章:GCC使用OpenMP_tugouxp的专栏-CSDN博客https://blog.csdn.net/tugouxp/article/details/119210576
OpenMP只在多核处理器上能够起到提高并行度的作用,在单核处理器上,使用OpenMP编译程序反而会降低性能。
这种方式是利用CPU,DSP中的专用VFP,SRAM硬件,在ISA层实现的加速指令。
这是比较常见的一种加速实现机制,比如CMSIS NN,BLAS,cudaNN等机制加速。