赞
踩
经过前面对NEON基础的学习,对NEON有了一定的了解, 现在正准备逐步开始学习NCNN,关于NCNN的入门介绍可以参考如下链接。
下面会逐步学习NCNN中src/layer/arm 文件夹中关于NEON的代码,只提取部分关于NEON的代码进行学习。
先从一个简单的实现学习吧,这个文件主要是用来对数据进行取绝对值的操作。在学习之前先看一个简单的在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;
}
这个文件中完成了部分激活函数在NEON上的实现,在学习这些激活函数之前,先了解一些NEON中基本的api用法吧。
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}
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}
float32x4_t v1 = {1,2,4,8};
auto res = vnegq_f32(v1); // {-1,-2,-4,-8}
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
}
}
本次学习了NCNN中求绝对值以及部分激活函数在NEON上的实现,但仍存在部分问题,例如tanh_ps中的许多数值计算的部分,后面有机会再进行详细的学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。