当前位置:   article > 正文

[NCNN学习笔记]-0

[NCNN学习笔记]-0
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

下面是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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
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}
  • 1
  • 2
  • 3
  • 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}
  • 1
  • 2
  • 3
  • 4
  • vnegq_f32, 取相反数
float32x4_t v1 = {1,2,4,8};
auto res = vnegq_f32(v1); // {-1,-2,-4,-8}
  • 1
  • 2
  • 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

下面开始对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
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
3、总结

本次学习了NCNN中求绝对值以及部分激活函数在NEON上的实现,但仍存在部分问题,例如tanh_ps中的许多数值计算的部分,后面有机会再进行详细的学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/284914
推荐阅读
相关标签
  

闽ICP备14008679号