从NCNN提取算子转为C——激活函数

本文介绍了如何将NCNN框架中的ReLU激活函数转换为C语言,详细解析了SSE2、AVX和AVX512F指令集在优化计算中的应用,并探讨了ReLU及其Leaky ReLU的实现细节。
摘要由CSDN通过智能技术生成

一、NCNN

链接1:https://github.com/Tencent/ncnn
链接2:https://download.csdn.net/download/weixin_43342667/88561077
NCNN是腾讯开发的神经网络推理框架,已开源,可适用于多种运行平台的部署,轻量高效、跨平台支持和高度可定制化。

二、激活函数

激活函数:在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是激活函数。引入激活函数是为了增加神经网络模型的非线性。没有激活函数的每层都相当于矩阵相乘。
这里以:ncnn-master\src\layer\x86\relu_x86.cpp\relu_x86.cpp 为例进行转换

2.1 前置了解

SSE2 : SSE2是Intel公司推出的一组指令集,用于加速浮点数运算,可以在CPU上同时处理多个数据,提高计算效率。SSE2指令集最早出现在Pentium 4和Athlon 64处理器中,现在已经成为主流CPU的标准配置。

在下面的代码中,使用了SSE2指令集来加速ReLU运算。例如,_mm_setzero_ps()是一个SSE2指令,用于将寄存器中的值设置为0,可以快速地将小于0的元素置为0。又如,_mm_max_ps()也是一个SSE2指令,用于对两个寄存器中的值进行比较,并返回较大的值,可以快速地完成ReLU运算。

需要注意的是,SSE2指令集只能加速浮点数运算,无法加速整数运算。在该代码中,如果输入数据类型为int8,则无法使用SSE2指令集进行加速,而是需要使用其他指令集(如AVX2)或者纯CPU计算来完成ReLU运算。

AVX : 全称——Advanced Vector Extensions,是Intel在Sandy Bridge处理器架构中引入的一组指令集扩展。AVX指令集进一步增强了CPU对向量化浮点运算的支持,可以在同一时钟周期内对更多的数据进行并行计算,提高计算效率。

与SSE2相比,AVX指令集引入了更长的128位和256位的矢量寄存器,可以同时处理更多的数据。AVX指令集还新增了一些用于浮点数计算的指令,如乘法和加法指令,可以在一个时钟周期内对多个数据进行乘法和加法运算,进一步提高了并行计算的效率。

在下面的代码中,如果CPU支持AVX指令集,并且输入数据类型为float或int16,则可以使用AVX指令集来加速ReLU运算。AVX指令集提供了更多的并行计算能力,可以在同一时钟周期内处理更多的数据,从而提高计算效率。

需要注意的是,AVX指令集需要CPU硬件的支持,较老的处理器可能不支持AVX指令集。在实际应用中,需要根据目标平台和要求来选择适当的指令集来进行优化。

AVX512F : 全称Advanced Vector Extensions-512,与之前的SSE和AVX指令集相比,AVX-512F提供了更长的512位矢量寄存器,可以同时处理更多的数据,进一步提高并行计算的能力。

AVX-512F指令集扩展提供了一系列用于浮点数和整数运算的指令,可以在一个时钟周期内对多个数据进行并行计算。它支持更高精度的浮点数运算、更广泛的数据类型(如浮点数、整数、逻辑和掩码等),以及更复杂的向量操作(如插入、提取、交换等)。

在下面的代码中,如果CPU支持AVX-512F指令集,并且输入数据类型为float或int16,则可以使用AVX-512F指令集来加速ReLU运算。AVX-512F拥有更大的向量寄存器和更丰富的指令集,可以进一步提高计算效率和并行性。

需要注意的是,AVX-512F指令集需要CPU硬件的支持,只有较新的处理器才能支持该指令集。在使用AVX-512F指令集时,还需要考虑功耗和散热等问题,因为AVX-512F指令集的运算复杂度较高,会产生较多的热量。因此,在实际应用中,需要综合考虑性能、功耗和散热等因素来选择是否使用AVX-512F指令集。

2.2 代码详解

下面的代码实现了一个ReLU(Rectified Linear Unit)激活函数的前向计算,使用了x86架构的优化实现,项目路径:ncnn-master\src\layer\x86\relu_x86.cpp\relu_x86.cpp


// 实现基于x86平台的ReLU激活函数。主要包含了使用SSE2、AVX和AVX512指令集进行加速的优化。
#include "relu_x86.h"

//在编译时检测是否支持 SSE2 和 AVX 指令集,若支持则包含相应的头文件,以便使用 SSE2 和 AVX 指令相关的函数和数据类型
#if __SSE2__	//检测是否包含SSE2
#include <emmintrin.h>	// <emmintrin.h>:定义了 SSE2 指令相关的函数和数据类型
#if __AVX__	//检测是否包含AVX
#include <immintrin.h>	// <immintrin.h>:定义了 AVX 指令相关的函数和数据类型
#endif // __AVX__
#endif // __SSE2__	//#endif:条件编译结束的标记,用于与 #if 相对应,表示以下的代码只有在条件成立时才会被编译

namespace ncnn {
   

ReLU_x86::ReLU_x86()
{
   
#if __SSE2__
    support_packing = true;	
    //根据是否支持SSE2指令集,设置了一个布尔值support_packing,用于判断是否支持数据打包
#endif // __SSE2__
}

int ReLU_x86::forward_inplace(Mat& bottom_top_blob, const Option& opt) const
//bottom_top_blob : 张量,包含了神经网络中的输入和输出数据
//opt : 选项对象,用于配置操作的行为。它包含了许多参数,例如线程数、卷积算法等,可以通过设置这些参数来优化算法的性能和效果。
//前向计算函数forward_inplace,接受输入输出数据bottom_top_blob和计算选项opt作为参数,并返回一个整型值。
{
   
    int elembits = bottom_top_blob.elembits();	//获取输入输出数据的元素位数,用于后续判断是否需要调用不同的前向计算函数。

    if (elembits == 8)
        return forward_inplace_int8(bottom_top_blob, opt);
//判断输入输出数据的元素位数是否为8位,如果是则调用forward_inplace_int8函数进行前向计算,并返回结果。

    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;
//获取输入输出数据的宽度w、高度h、通道数channels、元素打包数elempack以及总的元素数量size

    if (slope == 0.f)		//slope:斜率
    {
   
    	// 执行ReLU激活函数
    	// 在ReLU激活函数的实现中,使用了SIMD指令集进行优化。通过并行计算,提高了计算速度。
        #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;

// 三个条件编译:使用OpenMP进行并行计算,对每个通道的数据进行处理。根据是否支持AVX512F、AVX和SSE2指令集,选择不同的优化实现
#if __SSE2__
#if __AVX__
#if __AVX512F__
			//AVX512F 优化内容
			
            __m512 _zero_avx512 = _mm512_setzero_ps();
            //创建一个__m512类型的变量_zero_avx512,并将其初始化为全零的512位浮点数向量。

            for (; i + 15 < size; i += 16)
            //循环,每次处理16个元素,因为AVX512F指令集支持同时处理16个单精度浮点数。
            {
   
                __m512 _p = _mm512_loadu_ps(ptr);
                //从内存中加载16个单精度浮点数到一个__m512类型的变量_p中
                
                _mm512_storeu_ps(ptr, _mm512_max_ps(_zero_avx512, _p));
                //计算_p和_zero_avx512对应位置的最大值,并将结果存储回内存中。
                //这一步实际上就是把小于零的元素置为零,保持大于等于零的元素不变,完成了ReLU操作。
                
                ptr += 16;
                //移动指针到下一个16个元素的位置,以便进行下一轮的处理。
            }
#endif //
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IRUIRUI__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值