1、前言
经过前面对NEON基础的学习,对NEON有了一定的了解, 现在正准备逐步开始学习NCNN,关于NCNN的入门介绍可以参考如下链接。
2 、学习NCNN
下面会逐步学习NCNN中src/layer/arm 文件夹中关于NEON的代码,只提取部分关于NEON的代码进行学习。
2.1 absval_arm.cpp
先从一个简单的实现学习吧,这个文件主要是用来对数据进行取绝对值的操作。在学习之前先看一个简单的在NEON中取绝对值的例子
#include <arm_neon.h>
#include <iostream>
#include <vector>
using namespace std;
#include <arm_neon.h>
int main(){vector<float> data = {-1,-2,1,3};float32x4_t p0 = vld1q_f32(data.data());p0 = vabsq_f32(p0); vst1q_f32(data.data(), p0);for(auto d : data){cout << d << endl; // 1,2,1,3}return 0;
}
下面是NCNN中的实现,整体也十分简单,主要是对剩余向量进行处理方式。
int AbsVal_arm::forward_inplace(Mat& bottom_top_blob, const Option& opt) const
{int w = bottom_top_blob.w;int h = bottom_top_blob.h;int d = bottom_top_blob.d;int channels = bottom_top_blob.c;int elempack = bottom_top_blob.elempack;int size = w * h * d * elempack; // 数据类型占用的寄存器个数,参考 https://github.com/Tencent/ncnn/wiki/element-packing#pragma omp parallel for num_threads(opt.num_threads)for (int q = 0; q < channels; q++){float* ptr = bottom_top_blob.channel(q);int i = 0;
#if __ARM_NEON// 将长度为16的整数倍的数据进行向量化加速for (; i + 15 < size; i += 16){float32x4_t _p0 = vld1q_f32(ptr); // 提取4个float32的值到q寄存器float32x4_t _p1 = vld1q_f32(ptr + 4);float32x4_t _p2 = vld1q_f32(ptr + 8);float32x4_t _p3 = vld1q_f32(ptr + 12);_p0 = vabsq_f32(_p0); // 计算绝对值_p1 = vabsq_f32(_p1);_p2 = vabsq_f32(_p2);_p3 = vabsq_f32(_p3);vst1q_f32(ptr, _p0); // 结果写回vst1q_f32(ptr + 4, _p1);vst1q_f32(ptr + 8, _p2);vst1q_f32(ptr + 12, _p3);ptr += 16;}// 处理剩余数据// 处理8的倍数长度的数据for (; i + 7 < size; i += 8){float32x4_t _p0 = vld1q_f32(ptr);float32x4_t _p1 = vld1q_f32(ptr + 4);_p0 = vabsq_f32(_p0);_p1 = vabsq_f32(_p1);vst1q_f32(ptr, _p0);vst1q_f32(ptr + 4, _p1);ptr += 8;}// 处理4的倍数长度的数据for (; i + 3 < size; i += 4){float32x4_t _p = vld1q_f32(ptr);_p = vabsq_f32(_p);vst1q_f32(ptr, _p);ptr += 4;}
#endif // __ARM_NEON// c语言实现方式for (; i < size; i++){*ptr = *ptr > 0 ? *ptr : -*ptr;ptr++;}}return 0;
}
2.2 arm_activation.h
这个文件中完成了部分激活函数在NEON上的实现,在学习这些激活函数之前,先了解一些NEON中基本的api用法吧。
- vcleq_f32(v1,v2)逐元素比较,将v1小于v2的位置记为4294967295,反之记为0
float32x4_t _v = {-1, 2, -3, 4};
float32x4_t _zero = vdupq_n_f32(0.f);
uint32x4_t _lemask = vcleq_f32(_v, _zero); {4294967295,0,4294967295,0}
- vbslq_f32(mask,v1,v2) 根据mask中的元素,从v1和v2中挑选值,为4294967295选取v1中的元素,反之选取V2中的元素
float32x4_t a = {1,2,3,4};
float32x4_t b = {11,12,13,14};
uint32x4_t _lemask = {0,0,4294967295,4294967295};
auto res = vbslq_f32(_lemask, a, b); // {11,12,3,4}
- vnegq_f32, 取相反数
float32x4_t v1 = {1,2,4,8};
auto res = vnegq_f32(v1); // {-1,-2,-4,-8}
- vrecpeq_f32,取倒数,损失较大,需要额外操作增加精度
float32x4_t v1 = {1,2,4,8};
auto res = vrecpeq_f32(v1); // 0.98047 0.499023 0.249512 0.124756// vrecpsq_f32(a,b) = 2 - a * b
res = vmulq_f32(vrecpsq_f32(v1, res), res) // 0.999996 0.499998 0.249999 0.125
res = vmulq_f32(vrecpsq_f32(v1, res), res) // 1 0.5 0.25 0.125
下面开始对arm_activation.h 的几个激活函数开始学习
static inline float32x4_t activation_ps(float32x4_t _v, int activation_type, const ncnn::Mat& activation_params){if (activation_type == 1) // relu{ const float32x4_t _zero = vdupq_n_f32(0.f);_v = vmaxq_f32(_v, _zero); // 将小于0的元素变成0 }else if (activation_type == 2){ const float32x4_t _zero = vdupq_n_f32(0.f);const float32x4_t _slope = vdupq_n_f32(activation_params[0]); // 斜率const uint32x4_t _lemask = vcleq_f32(_v, _zero)float32x4_t _ps = vmulq_f32(_v, _slope);_v = vbslq_f32(_lemask, _ps, _v); // 通过_lemask从 _ps和_v中挑选数据} else if (activation_type == 3){const float32x4_t _min = vdupq_n_f32(activation_params[0]);const float32x4_t _max = vdupq_n_f32(activation_params[1]);_v = vmaxq_f32(_v, _min);_v = vminq_f32(_v, _max); // 直接限制最大最小值} else if (activation_type == 4){_v = sigmoid_ps(_v); // sigmoid}else if (activation_type == 5) // mish{_v = vmulq_f32(_v, tanh_ps(log_ps(vaddq_f32(exp_ps(_v), vdupq_n_f32(1.f))))); // mish}else if (activation_type == 6) // hardswish{const float alpha = activation_params[0];const float beta = activation_params[1];const float32x4_t _zero = vdupq_n_f32(0.f);const float32x4_t _one = vdupq_n_f32(1.f);float32x4_t _ans = vdupq_n_f32(beta); _ans = vmlaq_n_f32(_ans, _v, alpha); // beta + (_v * alpht)_ans = vmaxq_f32(_ans, _zero); _ans = vminq_f32(_ans, _one); _v = vmulq_f32(_ans, _v); // (beta + (_v * alpht)) * _v}}
3、总结
本次学习了NCNN中求绝对值以及部分激活函数在NEON上的实现,但仍存在部分问题,例如tanh_ps中的许多数值计算的部分,后面有机会再进行详细的学习。