人脸特征计算速度优化-SIMD技术Neon介绍

游走于秃头和研究的边缘

​关注

 
15 人赞同了该文章

目录
收起
1. baseline计算
2. simd和数据重排加速
数据重排Pack
simd加速
3. 循环展开
4. openmp的优化
5. openblas
6. 总的性能分析
7.结论:

之前在学习ARM的SIMD加速时学习到关系矩阵计算优化的部分,所以现在想来整理自己在计算人脸相似度计算的时候是如何进行优化的,以及与openblas的计算性能优化的比较。

首先原理我就不多做介绍了,大致就是一个向量1 x n和矩阵n x m相乘,最后得到然后计算得到的1 x m的结果,优化的原理跟向量矩阵乘法的优化类似,并比较计算最大值,我这里使用的是余弦距离,公式如下:

���(�,�)=�⋅�|�|⋅|�|=∑�=1���⋅��∑�=1���2⋅∑�=1���2
基本的优化:1)分母中 yi2 的值可提前计算,使用空间换时间,在计算之前提前计算好;2)x xi2 一般维度比较少,是一个vector * vector,simd优化空间不大,所以这里也不研究讨论,只优化x * y。

主要的优化方向

  1. 数据重排
  2. simd的neon加速
  3. openmp多cpu加速
  4. 其他

为了便于理解这里定义输入的向量大小是N,也就是人脸特征大小,M是代查询的人脸库的数量,对应的矩阵大小就是N x M,代码中使用num_input代表N,num_output代表M, bottom代表输入向量,feature代表人脸库

1. baseline计算

正常情况下的向量与矩阵的运算,就是两层for循环,矩阵存储一列代表一个人脸特征,大小是nums_input

  1. for (int i = 0; i < nums_output; i++) {
  2. for (int j = 0; j < nums_input; j++) {
  3. // 行优先遍历
  4. res[i] += features[i * nums_input + j] + bottom[j];
  5. }
  6. }

正常分析数据读取的顺序以及方式,存储方式如下,一行代表的是每个人脸特征,按行遍历,内层循环,每次计算与库中一个人脸的距离。

2. simd和数据重排加速

simd是可以同时计算多个变量,在这里指的是一次计算4个人脸的特征,伪代码如下:最基础的版本

  1. for (int i = 0; i < num_output / 4; i++) {
  2. for (int j = 0; j < num_input; j++) {
  3. // simd计算,每次访问四列的相同位置,
  4. // 第0行第一个位置x00,第1行第一个位置x10,第2行第一个位置x20,第三行第一个位置x30
  5. res[i] += features[(i + 0) * num_input + j] * bottom[j];
  6. res[i + 1] += features[(i + 1) * num_input + j] * bottom[j];
  7. res[i + 2] += features[(i + 2) * num_input + j] * bottom[j];
  8. res[i + 3] += features[(i + 3)* num_input + j] * bottom[j];
  9. }
  10. }

数据重排Pack

分析:每次访问时都是列优先访问,如遍历如下所示矩阵,按列优先访问x00, x10, x20,x30,每次跨度非常大,访问数据地址不连续,由于cache块的大小有限,不能一次加载所有的数据,如果nums_input太大的话,会出现大量的cache miss。

所以可以先对数据进行重排,也可称为是pack,pack我个人理解是将需要进行simd的数据,相邻的单元放到一起,这样内存加载到cache时,数据是相邻的,充分利用数据总线带宽,减少内存访问。

数据存储方式(M / pack) * N * (pack), pack这个数量跟微处理器架构以及指令集有关,比如说有的ARM的simd的指令集和微架构的支持128位,那么如果不做量化,就是128 / f32 = 4,如果使用半精度16,那就是128 / f16 = 8,pack的大小是8,在比如说x86架构的AVX指令集支持256,那就是256 / f32 = 8,pack大小就是8。我这里使用的ARM64位的支持ARM8指令集,使用的是128位的寄存器,所以是4。

数据重排pack的代码:

  1. void DataPack(float* feature, float* feature_pack,
  2. int num_output, int num_input, int pack_nums) {
  3. // 一行一个人脸特征,num_input
  4. for (int q = 0; q + (pack_nums - 1) < num_output; q += pack_nums) {
  5. float* f0 = feature + q * num_input;
  6. float* g0 = feature_pack + q * num_input;
  7. for (int p = 0; p < num_input; p++) {
  8. for (int j = 0; j < pack_nums; j++) {
  9. // x00 x10 x20 x30
  10. // x01 x11 x21 x31
  11. *g0++ = *(f0 + j * num_input + p);
  12. }
  13. }
  14. }
  15. }

转换的流程图如下,右边是pack的结果

实际就是将先将矩阵分块,每块是 pack x num_input 然后转置num_input x pack,改成行优先访问,现有访问顺序x00, x10, x20, x30,访问地址连续,充分利用数据总线带宽。

PS:如果说边界值不能被4整除,为了后续的计算方便,需要进行padding,我这里使用人脸库中最后一个人脸的特征进行padding,这样在最后计算最近相似距离时不需要进行任何处理。

simd加速

arm下可使用neon加速指令集,x86架构下可使用avx/sse指令集

以neon指令为例,简化代码版

  1. for (int i = 0; i < feat_nums; i += pack_nums) {
  2. float* g0 = features + i * feat_size;
  3. float32x4_t _sum0 = vdupq_n_f32(0.f); // 保存计算res[0],res[1],res[2],res[3]结果
  4. for (int j = 0; j < feat_size; j++) {
  5. float32x4_t q0 = vdupq_n_f32(bottom[j]); //加载输入数据b[0]
  6. float32x4_t m0 = vld1q_f32(g0); // 加载g[0][0],g[1][0],g[2][0],g[3][0]
  7. _sum0 = vmlaq_f32(_sum0, q0, m0); // 乘加计算
  8. g0 += 4;
  9. }
  10. vst1q_f32(res + i, _sum0);
  11. }

总的来说,就是i从0遍历到nums_input,每次计算 x0i, x1i, x2i, x3i 和 bi 的乘积并累加到 res0, res1, res2, res3 上

内层循环遍历结束后得到pack_nums(4)个人脸的相似度距离计算的结果。

3. 循环展开

循环展开是指通过将for循环中的代码,多复制几行,减少循环的次数,让每次循环执行更多的代码,我也找到了很多解释为什么会有优化效果:

并行编程方法与优化实践书:使用循环展开技术,以使用更多的寄存器,来解除数据依赖,填充浮点计算流水线
网上的说法:
第一,减少了分支预测失败的可能性。
第二,增加了循环体内语句并发执行的可能性,当然,这需要循环体内各语句不存在数据相关性。

关于并行编程方法与优化实践书中,我的个人理解是把不用的寄存器调用起来,当计算单元计算时,可以同时执行寄存器加载数据的指令,更好的使用指令流水线,,下面只展示循环展开的部分,当然还需要注意不能被4整除的边界处理和最后累加_sum0,_sum1,_sum2,_sum3的值

  1. for (int j = 0; j + 3 < feat_size; j += 4) {
  2. // unroll
  3. float32x4_t q0 = vdupq_n_f32(bottom[j]);
  4. float32x4_t m0 = vld1q_f32(g0);
  5. float32x4_t q1 = vdupq_n_f32(bottom[j + 1]);
  6. float32x4_t m1 = vld1q_f32(g0 + 4);
  7. float32x4_t q2 = vdupq_n_f32(bottom[j + 2]);
  8. float32x4_t m2 = vld1q_f32(g0 + 8);
  9. float32x4_t q3 = vdupq_n_f32(bottom[j + 3]);
  10. float32x4_t m3 = vld1q_f32(g0 + 12);
  11. _sum0 = vmlaq_f32(_sum0, q0, m0);
  12. _sum1 = vmlaq_f32(_sum1, q1, m1);
  13. _sum2 = vmlaq_f32(_sum2, q2, m2);
  14. _sum3 = vmlaq_f32(_sum3, q3, m3);
  15. g0 += 16;
  16. }
  17. // 寄存器累加
  18. _sum0 = vaddq_f32(_sum0, _sum1);
  19. _sum2 = vaddq_f32(_sum2, _sum3);
  20. _sum0 = vaddq_f32(_sum0, _sum2);
  21. vst1q_f32(res + i, _sum0);

ps:边界处理的代码我没有贴出来,其实就是for循环剩余的不能4个一起pack的数据。

4. openmp的优化

其实关于这部分的内容,我个人理解就是使用多个CPU核心去优化代码,上面的simd代码我使用的一个cpu去跑,那么如果我使用多个CPU核计算,每个CPU核计算一个pack块的结果不就行了,反正他们之前没有数据依赖。

代码也简单,只要在外层训练如下命令就行,4代表使用四个CPU核心,可自行修改,如果想更深入了解openmp的可参考OpenMP并行开发(C++)

  1. #pragma omp parallel for num_threads(4)
  2. for (int i = 0; i < feat_nums; i += pack_nums) {
  3. //每个pack块的计算
  4. }

5. openblas

OpenBlas中的cblas_sgemv的相关参数介绍

  1. cblas_sgemm(layout, transA, transB, M, N, K, alpha, A, LDA, B, LDB, beta, C, LDC);
  • layout:存储格式,有行主序(CblasRowMajor)和列主序(CblasColMajor),C/C++ 一般是行主序。
  • transA:A 矩阵是否需要转置。
  • transB:B 矩阵是否需要转置。
  • M,N,K:A 矩阵经过 transA 之后的维度是 M*K ,B 矩阵经过 transB 之后的维度是 K*N ,C 矩阵的维度是 M*N。
  • LDA,LDB,LDC:矩阵在 trans (如果需要转置)之前,在主维度方向的维度(如果是行主序,那这个参数就是列数)。
  1. // feat_nums 行
  2. // feat_size 列
  3. // CblasRowMajor 行主序
  4. // CblasNoTrans 不需要转置
  5. cblas_sgemv(CblasRowMajor, CblasNoTrans, feat_nums, feat_size, 1.0, features, feat_size, bottom, 1, 0.0, res, 1);

6. 总的性能分析

说明:

对比部分分为单核和4核,c++编译使用参数如下,使用O3的编译器优化,这里只使用了neon的intrinsic代码,没有使用assembly代码,等我想明白了,后面看看能不能用assembly优化

  1. -O3 -mcpu=cortex-a53 -fopenmp -lpthread -lopenblas

性能分析

使用如下方式测试芯片的实际极限的GFLOPS,测试之后结果是 9.32GFLOPS

数据大小2^15 x 128,这个大小基本上可以证明优化有效性,当然2^13, 2^14,2^16也是可以的,基本上都差不多,将原始进行归一化,计算加速比,单核效果

类型 时间 GFLOPS 加速比
原始 40.6741 ms 0.205541 1x
Openblas-1核 18.1682 ms 0.459087 2.23x
数据重排+SIMD 7.77417 ms 1.07619 5.23x
数据重排+SIMD+循环展开 6.40089 ms 1.30542 6.35x

其实这部分测试也不是很准确,因为我这里在计算的时候数据重排不累加在计算时间上,但是数据重排完全可以在矩阵向量乘法计算之前做,感觉Openblas的计算时间应该是包含了数据pack,也有可能是别的原因。

4核结果

类型 时间 GFLOPS 加速比
原始 40.6741 ms 0.205541 1x
Openblas 5.88533 ms 0.459087 6.91x
Openmp+数据重排+SIMD+循环展开 3.55032 ms 2.35354 11.45x

7.结论:

使用SIMD和一些优化手段确实可以实现提升速度的效果,但是跟实际的计算峰值还有很大的差距,还需要进一步优化,路漫漫其修远兮,吾将上下而求索~

[转帖]人脸特征计算速度优化-SIMD技术Neon介绍的更多相关文章

  1. 大文本 通过 hadoop spark map reduce 获取 特征列 的 属性值 计算速度

    大文本 通过 hadoop spark map reduce   获取 特征列  的 属性值  计算速度

  2. Dlib Python 检测人脸特征点 Face Landmark Detection

    首先安装Dlib,Opencv库 Dlib安装链接:http://www.cnblogs.com/as3asddd/p/7237280.html 环境:Mac Sierra 10.12.1 Pytho ...

  3. Tone Mapping算法系列二:一种自适应对数映射的高对比度图像显示技术及其速度优化。

    办公室今天停电,幸好本本还有电,同事们好多都去打麻将去了,话说麻将这东西玩起来也还是有味的,不过我感觉我是输了不舒服,赢了替输的人不舒服,所以干脆拜别麻坛四五年了,在办公室一个人整理下好久前的一片论文 ...

  4. java 虹软ArcFace 2.0,java SDK使用、人脸识别-抽取人脸特征并做比对

    java人脸识别 虹软ArcFace 2.0,java SDK使用.人脸识别-抽取人脸特征并做比对 虹软产品地址:http://ai.arcsoft.com.cn/product/arcface.ht ...

  5. [Pytorch]深度模型的显存计算以及优化

    原文链接:https://oldpan.me/archives/how-to-calculate-gpu-memory 前言 亲,显存炸了,你的显卡快冒烟了! torch.FatalError: cu ...

  6. dWebpack编译速度优化实战

    当你的应用的规模还很小时,你可能不会在乎Webpack的编译速度,无论使用3.X还是4.X版本,它都足够快,或者说至少没让你等得不耐烦.但随着业务的增多,嗖嗖嗖一下项目就有上百个组件了,也是件很简单的 ...

  7. [转]Asp.net mvc 网站之速度优化 -- 页面缓存

    网站速度优化的一般方法 由于网站最重要的用户体验就是速度,特别是对于电子商务网站而言. 一般网站速度优化会涉及到几个方面: 1. 数据库优化 — 查询字段简历索引,使用数据库连接池和持久化,现在还有种 ...

  8. web访问速度优化分析

    请求从发出到接收完成一共经历了DNS Lookup.Connecting.Blocking.Sending.Waiting和Receiving六个阶段,时间共计38ms.请求完成之后是DOM加载和页面 ...

  9. 记一次cocos项目的加载速度优化

    半个月前,我们用cosos creator做了一个简单的小游戏,也许算不上小游戏吧..一边学cocos,一边做,几经波折后终于上线了.然鹅,功能是实现了,但是加载速度十分感人(毕竟没经验嘛,无辜脸). ...

  10. Mysql数据库写入数据速度优化

    Mysql数据库写入数据速度优化 1)innodb_flush_log_at_trx_commit 默认值为1:设置为0,可以提高写入速度.  值为0:提升写入速度,但是安全方面较差,mysql服务器 ...

随机推荐

  1. CUDA C编程权威指南:1.2-CUDA基础知识点梳理

      主要整理了N多年前(2013年)学习CUDA的时候开始总结的知识点,好长时间不写CUDA代码了,现在LLM推理需要重新学习CUDA编程,看来出来混迟早要还的. 1.闭扫描和开扫描   对于一个二元 ...

  2. C# / VB.NET 获取PDF文档的数字签名信息

    文档中的数字签名具有不可否认性,可有效防伪防篡改.对文档中已有的数字签名信息,可通过一定方法获取,下面通过程序代码介绍如何来实现.程序中,使用了Spire.PDF.dll,版本:6.11.6,可自行在 ...

  3. 干货分享丨轻松玩转 Huawei LiteOS 传感框架

    摘要:LiteOS传感框架将物联网终端设备上不同类型的传感器统一管理,通过抽象不同类型传感器接口,屏蔽其硬件细节,做到"硬件"无关性,非常方便于物联网设备的开发.维护和功能扩展. ...

  4. 华为云MVP朱有鹏:做IoT开发乐趣无穷,年轻开发者更要厚积薄发

    [摘要] 可以预见的是,AIoT会是未来一段时间主流的技术趋势方向,当前也有不少科技巨头涌入其中,蓄势待发,而5G的到来加速了AIoT产业的扩张速度,所以如华为云MVP朱有鹏所说,年轻的开发者应该要拥 ...

  5. 过亿云资源运维管控难?华为云CloudMap带你喝着咖啡做运维

    摘要:华为云站点数字化平台CloudMap携手华为云图引擎GES打造云服务全栈拓扑,网络流量路径和云服务动态依赖等空间关系数据,支撑现网运行态风险识别和分钟级定位定界,构建业界领先的数字化能力. 本文 ...

  6. CWE 4.7中的新视图:工业控制系统的安全漏洞类别

    摘要:CWE今年的第一个版本在5/1前发布了,做为软件安全的重要分类标准,我们来看下这个版本有那些变化. 本文分享自华为云社区<CWE 4.7中的新视图 -- 工业控制系统的安全漏洞类别> ...

  7. git clone 出现fatal: unable to access ‘https://github 错误解决方法

    git clone 遇到问题:fatal: unable to access 'https://github.comxxxxxxxxxxx': Failed to connect to xxxxxxx ...

  8. ByteHouse+Apache Airflow:高效简化数据管理流程

    Apache Airflow 与 ByteHouse 相结合,为管理和执行数据流程提供了强大而高效的解决方案.本文突出了使用 Apache Airflow 与 ByteHouse 的主要优势和特点,展 ...

  9. 手把手教你在 Windows 环境中搭建 MQTT 服务器

    前言 前些天要对接一家硬件商的设备数据,对方使用的 MQTT 协议点对点透传,所以又赶紧搭建 MQTT 服务器,写 .NET 程序接收数据等等,今天分享一下如何搭建 MQTT 服务器. MQTT 协议 ...

  10. 提供免费 TPU 的 ControlNet 微调活动来啦

    相信大家已经感受到 AI 绘画的魅力,多多少少也可以自称半个「prompt 小专家」了,而在 AI 绘画的时候 Stable Diffusion 也会出现一些小瑕疵,比如 AI 不是灵魂画「手」,还有 ...