人工智能的发展为社会各个领域带来了无限可能,但这些应用都需要很强的计算性能和优化来提供准确、及时的结果。人工智能模型复杂性的增长速度是飞速的,大约三年前,像ELMo这样的自然语言模型只有9400万个参数,而今年最大的模型达到了超过1万亿个参数。
AVX-512VNNI:主要用于卷积神经网络的快速乘法累加。
AVX-512BF16:用于更快计算的低精度BFloat16浮点数。
图2.利用基础AVX512vs.利用AVX512VNNI做向量乘加
BFloat16(BF16)主要思想是提供16位浮点格式,其动态范围与标准IEEE-FP32相同,但精度较FP32变低。相当于指数区和FP32保持了相同的8位,并将FP32分数字段的小数区缩减到到了7位。大多数情况下,用户在进行神经网络计算时,BF16格式与FP32一样准确,但是以一半的位数完成任务。因此,与32位相比,采用BF16吞吐量可以翻倍,内存需求可以减半。此外,fp32到bf16的转化,相对于fp32到fp16的转化更加简单。
图3.BFloat16数据类型介绍
部署生态系统
2.1深度神经网络库OneDNN
帮助开发人员创建高性能深度学习框架。
支持Linux、Windows和macOS。
该底层库围绕四个概念建立:
原语(Primitive):是一个封装了特定计算过程的函数对象,特定计算过程如前向卷积,LSTM反向计算等。可以通过原语的属性来控制原语表达更复杂的融合操作,例如前向卷积和ReLU的融合。这里需要注意的是创建原语是重量级操作,最好的方式是一次创建、重复使用。或者可以通过缓存具有相同参数的原语来减少原语创建成本。
引擎(Engine):是计算设备的抽象,包括CPU,特定GPU卡等。创建的大多数原语是在一个特定引擎上执行计算,引擎类似于异构计算的host和device概念的统一,是可执行计算的设备的抽象。
流(Stream):是绑定到特定引擎的执行上下文的封装。
内存对象(memory):封装了分配给特定引擎的内存句柄、tensor的维度、数据类型、数据排布等信息。内存对象在执行期间被传递给原语进行处理。可以通过内存描述符(memory::desc)创建内存对象。
图4.OneDNN关系图
优势:
支持关键数据类型:float32、float16、bfloat16和int8。
实现了丰富的操作:convolution,matrixmultiplication,pooling,batchnormalization,activationfunctions,recurrentneuralnetwork(RNN)cells,andlongshort-termmemory(LSTM)cells。
2.2优化深度学习框架:TensorFlow,PyTorch
深度学习框架通过高级编程接口为数据科学家、AI开发人员和研究人员提供模块,以构建、训练、验证和部署模型。
1)在算子优化部分,将默认的(Eigen)内核替换为(使用oneDNN)高度优化的内核,OneDNN优化了一系列TensorFlow操作。OneDNN库是开源的,在构建TensorFlow时自动下载。
算子融合
算子融合是一个常见的性能优化方法,在融合之前,每个算子计算前后都需要把数据从内存读到缓存,再从缓存写回到内存。而融合之后,可以避免算子之间内存读写从而提高性能。
图5.算子融合
所以在做算子融合的时候本质上便是把多个Tensor整合到一个Node下。虽然只是简单的算子融合,但是在计算过程中可以提高速度。
布局传播
在布局传播方面,数据布局是会很大程度影响性能的,因此,我们需要做到:
顺序访问;尽可能在最内层的循环中进行迭代,提高向量利用率;尽可能的重用数据,例如卷积层中的权重。
通常,对于CPU上的某些张量操作,原生TensorFlow数据格式并不是最有效的数据布局。对于TensorFlow中的tensor,所有OneDNN中的算子使用都是高度优化的数据布局。在这种情况下,我们插入一个从TensorFlow原生格式到内部格式的数据布局转换操作,在CPU上执行操作,并将操作输出转换回TensorFlow原生格式。需要注意的是我们应该避免冗余的转化开销。而这些转换会带来性能开销,因此带来的挑战在于如何避免不必要的转换。所以,我们应该采用布局传播方式,布局传播就是用于识别出相邻的互逆reorder,并消除它们,从而减少了无效的操作。也就是最右边的这张图中的结构。
图6.布局传播
查找子图,这个子图中所有算子都有OneDNN支持
在这样的子图的边界上,引入数据布局转换
IPEX可以作为Python程序中的模块进行加载,也可以作为C++程序的C++库链接。用户可以通过导入intel_extension_for_pytorch在脚本中动态启用这些优化。
优化扩展包IPEX中所包含的优化有:
图优化:IPEX支持常用的算子融合,如Conv2D+ReLU,Linear+ReLU等。算子融合带来的好处以透明的方式传递给用户。IPEX支持FP32和BF16融合模式,以及INT8融合模式。
Runtime优化:为用户提供了多个PyTorch前端API,以便对线程运行时进行更细粒度的控制。
通过Python前端模块MultiStreamModule进行多流推理。
从Python和C++前端生成异步任务。
从Python和C++前端为OpenMP线程配置核心绑定。
2.3优化工具INC
图8.INC基础架构图
可以看到,这个工具支持自动的精度驱动的调优策略,帮助用户快速找到最佳量化模型。这些调优策略包括贝叶斯/MSE/TPE…
对于量化部分,模型量化主要是通过降低模型中tensor和weights精度的手段,从而减少计算需求和数据存储与传输需求,来达到加速的目的。主要方法分两派:一是训练后量化(Post-trainingQuantization),二是量化感知训练(Quantization-AwareTraining)。
INC工具支持
训练后静态量化(Post-trainingStaticQuatization)
训练后动态量化(PosttrainingdynamicQuatization)
量化感知训练(Quantization-awaretraining)
对于剪枝部分,深度学习网络模型从卷积层到全连接层存在着大量冗余的参数,大量神经元激活值趋近于0,将这些神经元去除后可以表现出同样的模型表达能力,这种情况被称为过参数化,而对应的技术则被称为模型剪枝。INC中采用多种剪枝算法,如非结构化剪枝(Magnitude-based基于幅度剪枝),结构化剪枝(Gradientsensitivity梯度敏感剪枝),生成具有预定义稀疏性目标的剪枝模型。
以在TensorFlow上的使用举例,用户模型可以是savedmodel、ckpt、pb各种形式,通过INC工具运行后,得到一个优化后的模型,依然为之前的用户模型格式,可以在Tensorflow上使用,相较于原始模型来说获得更好的性能效果。
图9.INC性能表现
这是在150+个业内常用的模型,使用INC工作后的评估结果。这些模型涉及各个领域,包括图像分类中常用的ResNet、GoogLeNet、Inception,目标检测中常用的Mobilenet、Yolo、FasterRCNN,推荐中常用的Wide&deep、DLRM,自然语言处理中常用的bert等。这张图展现的是int8相对于fp32的性能结果比较。横轴为相应的精度改变,纵轴为Int8与fp32相比在实时latency的提升。可以看到几何平均提升达到2.3倍。
3.1性能优化方法
在推理的性能优化方面,工作可归成四类:算子优化、图优化、模型压缩和部署优化。
算子优化:算子优化中微架构优化的主要焦点是如何充分利用好微架构的内置加速器的能力去最大化算子的性能。OneDNN底层库就可以帮大家很好的做这部分的优化。
模型压缩:如果还需要额外的性能增益,这时候需要考虑模型压缩方案。模型压缩(Compression)主要手段有:模型量化、剪枝和模型蒸馏。工程师们也可以通过刚刚介绍的INC压缩工具来便捷的使用到这些压缩方案。
部署优化:主要通过调整模型在部署时的资源分配和调度的参数来进一步优化性能。
在TensorFlow中,“前端”负责图描述,“后端”负责算子的执行。因此,我们可以将模型从原始BERT模型转换为前端带有BERTop的新模型,并将新的BERT内核实现注册到后端。因此,我们不需要重新安装TensorFlow框架。我们只需要加载实现BERT代码的动态库即可,并通过oneAPI深度神经网络库(oneDNN)等高性能工具,来提升性能。
图10.BERT模型优化方案
对于优化实现,我们分析BERT图进行层融合和张量融合。这张图显示了12层BERT中的一层细节。
在这个案例中,又可以进行横向融合和纵向融合。关于横向融合,我们可以看到三个张量,即查询query、键key和值value,都需要进行MatMul和BiasAdd操作。我们可以把这些操作融合为一个操作,即QKVMatMul和BiasAdd。关于纵向融合,可以参照下图。
图11.新BERT模型实现
为了减少内存访问,我们将FP32权重转换为BF16权重,像下图中右边的图结构所示。我们将FP32权重转换为BF16权重并将BF16权重缓存在BF16MatMulop中以供重用,并在每次执行时并行的将FP32输入转换为BF16输入。
图12.BFloat16优化方案
在这样的转化下,我们可以使用bfloat16的点积计算MatMulop,可以看到输入为BF16类型,输出为fp32类型。这些实现是利用了oneDNN的支持。因此,我们只需要创建一个新的BF16MatMulop来替换优化的FP32解决方案(Baseline)MatMulop,然后我们就可以实现BF16与FP32优化相比带来的性能提升。
对于BF16优化方案,通过简单的运算替换来提高性能,可以保持尽可能高的精度。对于BiasAdd操作,我们仍然保持FP32操作,以减少精度损失。
最后是一个优化后方案的性能和进度评估结果,为了比较优化后的FP32Bert和优化后的BF16Bert的性能差异,我们将batchsize设为1,tokensize设为128,这也符合实际的线上业务。输入是MRPC数据集。以延时为21.70毫秒的FP32解决方案为基准,可以看到优化的BF16解决方案与基线相比,延迟为11.83毫秒,端到端的性能提升达到了1.83倍,并且没有Accuracy的损失。