作者 | OPEN AI LAB 研究员 吕春莹

出品 | AI科技大本营

头图 | CSDN下载自视觉中国

随着AI技术的快速发展,深度学习在各个领域得到了广泛应用。深度学习模型能否成功在终端落地应用,满足产品需求,一个关键的指标就是神经网络模型的推理性能。于是,一大波算法工程师为了算法的部署转岗算子优化工程师。然而,优化代码并不是一件简单的事,它要求工程师既要精通计算机体系架构,又要熟悉算法的计算流程,于是,稍微有经验的深度学习推理优化工程师都成了各家公司争抢的“香饽饽”。相关人才少,但需求多,算子优化自动化成为了未来的一大趋势。

为了方便更多的工程师进行推理优化,一个致力于降低优化门槛,提升优化开发效率的算子自动优化工具AutoKernel宣布正式开源!

AutoKernel特色:

  • 低门槛: 无需底层优化汇编的知识门槛

  • 简单易用: 提供docker环境,无需安装环境,plugin一键集成到推理框架Tengine

  • 高效率: 无需手写优化汇编,一键生成优化代码,一键部署

AutoKernel使用业界广泛使用的自动代码生成项目Halide,通过输入计算描述和调度策略,自动生成底层代码。AutoKernel支持以plugin的形式,将生成的自动优化算子一键部署到推理框架Tengine中。

下面,本教程将带领大家一步步优化矩阵乘法GEMM。无需手工撸代码,编写繁杂冗长的底层汇编代码,只需十几行简洁的调度代码。

在详细讲解优化步骤前,我们先谈谈优化的本质。我们在谈”优化“的时候,计算机底层做了什么?优化的”瓶颈“是什么?为什么通过一波”优化操作“,性能就能提升呢?AutoKernel使用的Halide是如何实现自动优化的呢?

要解答这些疑问,我们需要了解一下硬件的基础的体系结构,了解硬件如何工作,才能在软件上实现算法的时候,尽可能去考虑利用硬件的一些特性,来做到高效的、极致的优化。

上图是典型的存储理器层次结构:主存容量大,访问速度慢,寄存器和缓存读取速度快,但容量有限。在寄存器的层级上,CPU可以在一个时钟周期内访问它们,如果CPU去访问外部的DDR的话,延迟是非常大的,大概是200个时钟周期左右。如果CPU去访问cache的话,一般需要6到12个cycle就够了。所以,一个很重要的一个优化宗旨是:优化内存访问,充分利用寄存器和高速缓存去存数据。

第二个优化宗旨则是提高并行性:充分利用SIMD进行指令向量化和多核心并行。大部分现代CPU支持SIMD(Single Instruction Multiple Data,单指令流多数据流)。在同一个CPU循环中,SIMD可在多个值上同时执行相同的运算/指令。如果我们在4个数据点上进行向量化,一次计算四个数据,理论上就可以实现4倍的加速。

运行环境搭建

AutoKernel提供了docker镜像,docker里已经配置好运行环境,进入docker即可直接运行demo代码:

# 拉取镜像docker pull openailab/autokernel# 启动容器,进入开发环境docker run -it openailab/autokernel /bin/bash# 获取代码git clone https://github.com/OAID/AutoKernel.gitcd AutoKernel/doc/tutorials/data/

目录下的build.sh是demo的执行脚本,运行需要指定优化步骤step,可选的step是从1 到7,其中step= 1 是默认不优化的,step=7是最极致优化的。

优化效果

# 执行demo./build.sh 1./build.sh 7

下图展示了在Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz的电脑上的优化效果,无需手工撸代码,无需编写繁杂冗长的底层汇编代码,只需十几行简洁的调度代码, 就能性能优化200+倍~

优化步骤

以下是更为详细的优化步骤:

STEP1

第一个步骤是不带任何优化的。用Halide语言直接描述GEMM的计算过程。

Var x,y;  RDom k(0, K);  Func gemm("gemm"); gemm(x, y) += A(k, y) * B(x, k);

计算M=N=K=640的矩阵乘法。运行脚本第一个参数指定step=1。耗时结果如下:

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 1step =  1M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      240.8523 ms     1.1376 ms

STEP2

这一步我们采用分块tile。分块的目的是为了充分利用缓存。如果原来的循环较大,tile分块改成小块数据去计算,可以使得每次计算的数据都比较舒适地呆在缓存里,不用经历重复的驱逐(在缓存中重复的添加和删除数据)。分块后进行reorder操作,交换两个嵌套循环的顺序,目的是最内层的内存访问友好。我们按照x,y维度划分成16x8的小分块去计算:

.gemm.update()  .tile(x, y, xo, yo, xi, yi, 16, 8)  .reorder(xi, yi, k, xo, yo);

执行结果如下:

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 2step =  2M N K = 640 640 640     err 0.00        [rep 50] halide | blas  81.8148 ms      1.1281 ms

性能从240ms优化到82ms,提升了近3倍。

STEP3

我们在上一步的基础上增加向量化vectorize。向量化是把几个标量计算(scale)转换为一个向量计算(vector),充分利用SIMD向量指令。大部分现代CPU支持SIMD(Single Instruction Multiple Data,单指令流多数据流)。在同一个CPU循环中,SIMD可在多个值上同时执行相同的运算/指令。

gemm.update()  .tile(x, y, xo, yo, xi, yi, 16, 8)  .reorder(xi, yi, k, xo, yo)  .vectorize(xi, 8);

执行结果:

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 3step =  3M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      27.5433 ms      1.1445 ms

性能从82ms优化到27ms,又加速了接近3倍。可以看到,围绕前面提到的两条优化宗旨:优化内存访问和提高并行性,从step1到step3,性能已经提升了近9倍。

STEP4

调度策略在step3的基础上增加并行化parallel。对一个循环并行化是把循环的每次迭代分给多个线程或者处理器去同时处理,每个线程处理通过代码段(loop body),但是处理不同的数据。

gemm(x, y) += A(k, y) * B(x, k);  gemm.update()  .tile(x, y, xo, yo, xi, yi, 16, 8)  .reorder(xi, yi, k, xo, yo)  .vectorize(xi, 8)  .parallel(yo);

执行结果:

root@bd3faab0f079:/home/chunying/AutoKernel/doc/tutorials# ./06_build.sh 4step =  4M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      7.2605 ms       1.1605 ms

增加并行化后,build.sh默认指定四线程,性能直接翻了近4倍,从27ms到7.3ms.

STEP5

调度策略在上一步的基础上增加unroll展开。如果循环体内的语句没有数据相关依赖,循环展开可以增加并发执行的机会,使得更充分利用寄存器,减少循环时每个操作内存加载和保存的次数。

gemm.update()  .tile(x, y, xo, yo, xi, yi, 16, 8)  .reorder(xi, yi, k, xo, yo)  .vectorize(xi, 8)  .parallel(yo)  .unroll(xi)  .unroll(yi,2);

执行结果:

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 5step =  5M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      4.7617 ms       1.1597 ms

unroll展开后,性能从7.3ms优化到4.8ms.

STEP6

前面的分块成 16 x 8的小kernel, 这一步先划分成 16 x 32的分块,然后把每个分块再分成 16 x 8的子分块。我们把最外层的两层循环合并到一层,并对这一层进行并行化。这一步计算描述多了一个prod函数来定义子分块的计算,prod函数的计算公式和总的gemm是一样的,我们通过 compute_at指定在 yi维度之下计算prod,则prod计算的是 16x8的小kernel, 大致逻辑如下:

总的代码如下:

Func prod;  prod(x, y) += A(k, y) * B(x, k);  gemm(x, y) = prod(x, y);  gemm.tile(x, y, xi, yi, 16, 32)  .fuse(x, y, xy).parallel(xy)   .split(yi, yi, yii, 4)  .vectorize(xi, 8)  .unroll(xi)  .unroll(yii);  prod.compute_at(gemm, yi)  .vectorize(x, 8).unroll(y);  prod.update()  .reorder(x, y, k)  .vectorize(x, 8)  .unroll(x)  .unroll(y)   .unroll(k, 2);

执行结果

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 6step =  6M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      3.1824 ms       1.1373 ms

这一步距离STEP1性能已经优化了近80倍了,性能越来越接近OpenBlas了。

STEP 7

这一步添加的操作是对矩阵B进行数据重排,使得在计算小kernel 16x8时,内存读取更顺畅。因为小kernel的x维度是按照16划分的,因此重排数据B的x维度也是按照16重排。

总的代码如下:

Func B_interleave("B"), Bs("Bs");  Bs(x, y, xo) = B(xo * 16 + x, y);  B_interleave(x, y) = Bs(x % 16, y, x / 16);  Func prod;  prod(x, y) += A(k, y) * B_interleave(x, k);  gemm(x, y) = prod(x, y);  gemm.tile(x, y, xi, yi, 16, 32)
.fuse(x, y, xy).parallel(xy)  .split(yi, yi, yii, 4)  .vectorize(xi, 8)  .unroll(xi)  .unroll(yii);  prod.compute_at(gemm, yi)  .vectorize(x, 8).unroll(y);  prod.update()  .reorder(x, y, k)  .vectorize(x, 8)  .unroll(x)  .unroll(y)  .unroll(k, 2);  Bs.compute_root()  .split(y, yo, yi, 16)  .reorder(x, yi, xo, yo)  .unroll(x)
.vectorize(yi).parallel(yo, 4);

执行结果:

root@bd3faab0f079:/AutoKernel/doc/tutorials/data# ./06_build.sh 7step =  7M N K = 640 640 640     err 0.00        [rep 50] autokernel | blas      1.1957 ms       1.1425 ms

至此,我们的每一步调优策略始终都围绕两条优化宗旨“优化内存访问”,“提高并行性”展开优化,到最后性能已经与OpenBlAS差不多了,距离STEP1已经加速了200+倍了。

项目地址:

https://github.com/OAID/AutoKernel/

更多精彩推荐
  • 赠书 | 实现病人数据自动分析建模,Python能做的比你想象得更多

  • 为什么苹果M1芯片这么快?

  • 实战|手把手教你用Python爬取存储数据,还能自动在Excel中可视化

  • 常说的「缓存穿透」和「击穿」是什么

  • C语言能够被替换吗?

GEMM性能提升200倍,AutoKernel算子优化工具正式开源相关推荐

  1. GEMM与AutoKernel算子优化

    GEMM与AutoKernel算子优化 随着AI技术的快速发展,深度学习在各个领域得到了广泛应用.深度学习模型能否成功在终端落地应用,满足产品需求,一个关键的指标就是神经网络模型的推理性能.一大波算法 ...

  2. Hologres揭秘:优化COPY,批量导入性能提升5倍+

    简介:揭秘Hologres优化COPY的技术原理,实现批量导入性能提升5倍+ Hologres(中文名交互式分析)是阿里云自研的一站式实时数仓,这个云原生系统融合了实时服务和分析大数据的场景,全面兼容 ...

  3. nginx php7提速,nginx+php7-fpm 性能提升几倍跟踪实践结果并优化

    nginx+php7-fpm 性能提升几倍跟踪实践结果并优化 nginx+php7-fpm 性能提升几倍,跟踪实践结果并优化 历史ubuntu服务器使用的apache+php5,现在使用nginux+ ...

  4. Nacos 2.0 性能提升十倍,贡献者 80% 以上来自阿里之外

    来源 | 阿里巴巴云原生公众号 3 月 20 日,Nacos 2.0 正式发布.Nacos 是阿里巴巴在 2018 年开源的一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台,也可以理解为 ...

  5. 重磅官宣:Nacos2.0性能提升10倍

    简介:​Nacos2.0 作为一个跨代版本,彻底解决了 Nacos1.X 的性能问题,将性能提升了 10 倍. 作者:席翁 继 Nacos 1.0 发布以来,Nacos 迅速被成千上万家企业采用,并构 ...

  6. 查询速度提升200倍,ClickHouse到底有多快?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取个gui 来源:r6a.cn/a8UZ ClickHouse 是 Yande ...

  7. 腾讯云数据库开源再突破:TDSQL PG版查询性能提升百倍

    日前,腾讯云数据库开源产品TDSQL PG版(开源代号TBase)宣布推出重磅升级--经过一年半的打磨,上万张表访问场景下,内存占用节省60%:查询性能提升百倍:SQL语句兼容性增强.同时,大力提升原 ...

  8. 握草!查询提升200倍,它难道想干掉传统数据库?

    ClickHouse 是 Yandex(俄罗斯最大的搜索引擎)开源的一个用于实时数据分析的基于列存储的数据库,其处理数据的速度比传统方法快 100-1000 倍. ClickHouse 的性能超过了目 ...

  9. 查询提升200倍,ClickHouse你值得拥有!

    来源:https://juejin.im/post/6863283398727860238 一.ClickHouse 是什么? ClickHouse:是一个用于联机分析(OLAP)的列式数据库管理系统 ...

最新文章

  1. pytorch 冻结层操作 + 学习率超参数设置
  2. 阿里邮箱发布“Mail+”战略 有望与畅捷通工作圈互通互联
  3. Winform中设置ZedGraph因设置小刻度导致的竖直虚线显示过多
  4. 我的MVC之旅(3)--------MVC Music Store 第三篇 Views and ViewModels [翻译]
  5. 手机技巧:微信这个“设置”建议关闭!否则不到半年就卡爆了
  6. Rx2.0后台开发分享
  7. 不要再纠结卷积的公式啦!0公式深度解析全连接前馈网络与卷积神经网络
  8. php实验星星塔,简单编程(九)编程制作特殊图案 星星塔(3) 左右星星塔 for循环的嵌套编程...
  9. (转)SpringMVC学习(十二)——SpringMVC中的拦截器
  10. Python之数据分析(Numpy的数组切片、数组变维、组合与拆分)
  11. [转载] [Python] np.ones_like(ndarray)和np.zeros_like(ndarray)
  12. Apache 模块 mod_cache应用
  13. php函数体用return,php递归函数使用return问题
  14. QQ拼音输入法词库和搜狗输入法词库[相互导入](使用Excel公式)
  15. 3DGIS 与 BIM 融合技术方案
  16. 华为服务器如何正确加装扩展内存
  17. java 对象 转为繁体,java调用opencc,将简体中文转换成繁体
  18. Perfetto for linux-使用 Perfetto 分析调度问题
  19. 硬盘 SMART 检测参数详解
  20. ios silk to MP3

热门文章

  1. Ubuntu命令终端查看使用过的命令
  2. 常用API(Object、String、StringBuffer、用户登陆注册)
  3. WinForm容器内控件批量效验是否允许为空?设置是否只读?设置是否可用等方法分享...
  4. session 与 cookie的区别
  5. OnCheckedChanged的触发需要AutoPostBack=true
  6. 关于ORA-01950: no privileges on tablespace 的解决
  7. AS1.0(2.0)中的XML示例
  8. php获取WdatePicker值,WdatePicker日历控件使用方法
  9. Directx11 教程(2) 基本的windows应用程序框架(2)
  10. CMAKE设置INSTALL工程,分别设置头文件、Lib和DLL的输出路径