KuiperInfer深度学习推理框架-源码阅读和二次开发(2):算子开发流程(以sigmoid为例)

news2024/10/7 14:23:49

前言:KuiperInfer是一个从零实现一个高性能的深度学习推理库,中文教程已经非常完善了。本系列博客主要是自己学习的一点笔记和二次开发的教程,欢迎更多的AI推理爱好者一起来玩。这篇写一下算子开发流程,以sigmoid算子为例,为下一节我们自己手写算子打下基础。

目录

sigmoid算子原理

预处理指令#pragma

OpenMP并行处理

OpenMP简介

#pragma omp parallel for num_threads()

编译 -fopenmp选项

算子开发模板

explicit修饰构造函数

重载函数 Forward()

单例模式 GetInstance()

测试文件

NCNN实现借鉴

参考


sigmoid算子原理

在写算子之前需要对算子的功能非常熟悉, 选取这个算子讲解的原因有两个:

1、深度学习中最常用的激活函数之一。

2、公式非常简单:

 具体来说有如下特点:

  • 神经网络中的激活函数, 其作用就是引入非线性;
  • 输出范围有限, 数据在传递的过程中不容易发散; 缺点是饱和的时候梯度太小;
  • 输出范围为(0, 1), 所以可以用作输出层, 输出表示概率;
  • 容易求导, y'=y(1-y), 这样很容易做bp(back propagation, 反向传播).

预处理指令#pragma

C程序在整个编译过程中要经过一下这几步:

步骤过程指令
预处理展开头文件/宏替换/去掉注释/条件编译(test.i main .i)
编译检查语法,生成汇编( test.s main .s)
汇编汇编代码转换机器码(test.o main.o)
链接链接到一起生成可执行程序a.out

在C/C++中共有以下预处理指令:

 pragma便是预处理指令的一种,这条指令时用来制定不同的编译器选项。这些选项根据平台和所使用的编译器各有不同。查询你所使用的编译器的手册或者参考文件,找到你能使用#pragma定义的参数的信息。

如果编译器不支持#pragma里面的参数,那么就会忽略这条命令,不会产生任何语法错误。

OpenMP并行处理

kuiper中#pragma只使用了#pragma omp parallel,这是一个并行操作。其他的指令后面遇到后再学习吧,先学习这一个。

OpenMP简介

OpenMP 是由 OpenMP Architecture Review Board 牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套编译指令 (Compiler Directive)。OpenMP 支持的编程语言包括 C 语言、C++ 和 Fortran;而支持 OpenMP 的编译器包括 Sun Compiler,GNU Compiler 和 Intel Compiler 等。OpenMP 提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的 pragma 来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些 pragma,或者编译器不支持 OpenMP 时,程序又可退化为通常的程序 (一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。

OpenMP 提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本 身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP 是一个很好的选择。同时,使用 OpenMP 也提供了更强的灵活性,可以较容易的适 应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在 OpenMP 中,OpenMP 库从程序员手中接管了部分这两方面的工作。

#pragma omp parallel for num_threads()

  const uint32_t batch_size = inputs.size();
#pragma omp parallel for num_threads(batch_size)
  for (uint32_t i = 0; i < batch_size; ++i) {
    const std::shared_ptr<Tensor<float>> &input = inputs.at(i);
    CHECK(input == nullptr || !input->empty()) << "The input feature map of sigmoid layer is empty!";

    std::shared_ptr<Tensor<float>> output = outputs.at(i);
    if (output == nullptr || output->empty()) {
      output = std::make_shared<Tensor<float>>(input->shapes());
      outputs.at(i) = output;
    }

    CHECK (output->shapes() == input->shapes()) << "The output size of sigmoid is error";
    output->set_data(input->data());
    output->Transform([](const float value) {
      return 1.f / (1 + expf(-value));
    });
  }

因为for是一个语句,所以就没有#pragma大括号括起来了。根据input的size开启相同数量的线程(如果input的size过大,会不会造成线程数量过多?),每个线程先取出特征,在做了一系列的异常处理之后,核心操作就是这句话:

    output->set_data(input->data());
    output->Transform([](const float value) {
      return 1.f / (1 + expf(-value));
    });

这里用了匿名函数,我也不知道为什么特别喜欢用匿名函数,但是我看paddle CINN里面也是大量使用,能明白其含义并且照葫芦画瓢即可。

编译 -fopenmp选项

这种创建多线程的方式简单高效,但是有一点必须注意,#pragma omp parallel关键字创建多线程必须在编译时加上-fopenmp选

项,否则起不到并行的效果,

g++ a.cc -fopenmp

算子开发模板

其实核心就两个函数,一个是重载的Forward()函数,另一个单例模式中的GetInstance()函数。

  • 构造函数,涉及知识点:explicit用于禁止隐式转换
  • Forward()函数,用于核心算子功能实现。
  • GetInstance()函数,用于单例模式方法调用。

explicit修饰构造函数

圣经《Effective C++》在导读中这样写到:

被声明为explicit的构造函数通常比其 non-explicit 兄弟更受欢迎, 因为它们禁止编译器执行非预期 (往往也不被期望) 的类型转换. 除非我有一个好理由允许构造函数被用于隐式类型转换, 否则我会把它声明为explicit. 我鼓励你遵循相同的政策.

我觉得这篇博客里举例的隐式调用规则说的比较清楚,建议学习一下:

C++ explicit 关键字 - 知乎

所以在kuiper里面所有算子的构造函数都被explicit修饰。

  explicit SigmoidLayer(): Layer("Sigmoid"){

  }

重载函数 Forward()

C++ override从字面意思上,是覆盖的意思,实际上在C++中它是覆盖了一个方法并且对其重写,从而达到不同的作用。override是C++11中的一个继承控制关键字。override确保在派生类中声明的重载函数跟基类的虚函数有相同的声明。

override明确地表示一个函数是对基类中一个虚函数的重载。更重要的是,它会检查基类虚函数和派生类中重载函数的签名不匹配问题。如果签名不匹配,编译器会发出错误信息。

因为基类Layer的Forward是虚函数,所以子类需要用override重载

  /**
   * Layer的执行函数
   * @param inputs 层的输入
   * @param outputs 层的输出
   * @return 执行的状态
   */
  virtual InferStatus Forward(
      const std::vector<std::shared_ptr<Tensor<float>>>& inputs,
      std::vector<std::shared_ptr<Tensor<float>>>& outputs);

  /**
   * Layer的执行函数
   * @param current_operator 当前的operator
   * @return 执行的状态
   */
  virtual InferStatus Forward();
  InferStatus Forward(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                      std::vector<std::shared_ptr<Tensor<float>>> &outputs) override;

单例模式 GetInstance()

这个上一讲详细讲过,不多赘述。

KuiperInfer深度学习推理框架-源码阅读和二次开发(1):算子开发流程之算子注册机制详解_沉迷单车的追风少年的博客-CSDN博客

测试文件

单测能力是依赖Google Test的,代码中的TEST是一个宏,用来创建测试用例,它有test_case_name和test_name两个参数,分别是测试用例名和测试名

一些基础的测试知识请看:

一文掌握google开源单元测试框架Google Test - 知乎

我们如何测试算子是否正确呢?我们需要“手动”实现一遍逻辑,然后看这两者之间的结果是否相等。

这里手动实现的核心是我们要调用 1.f / (1 + std::exp(-input_->index(j)) 来验证是否正确,注意单元测试要尽可能覆盖所有特殊的样例!

TEST(test_layer, forward_sigmoid4) {
  using namespace kuiper_infer;
  std::shared_ptr<Tensor<float>> input = std::make_shared<Tensor<float>>(1, 32, 128);
  input->Rand();
  std::vector<std::shared_ptr<Tensor<float>>> inputs;
  inputs.push_back(input);
  std::vector<std::shared_ptr<Tensor<float>>> outputs(1);

  SigmoidLayer sigmoid_layer;
  const auto status = sigmoid_layer.Forward(inputs, outputs);
  ASSERT_EQ(status, InferStatus::kInferSuccess);
  for (int i = 0; i < inputs.size(); ++i) {
    std::shared_ptr<Tensor<float>> input_ = inputs.at(i);
    std::shared_ptr<Tensor<float>> output_ = outputs.at(i);
    CHECK(input_->size() == output_->size());
    uint32_t size = input_->size();
    for (uint32_t j = 0; j < size; ++j) {
      ASSERT_EQ(output_->index(j), 1.f / (1 + std::exp(-input_->index(j))));
    }
  }
}

单测当中就没有必要写#program并行了,直接逐个遍历即可。

NCNN实现借鉴

NCNN当然要认真抄袭借鉴了!实现在这个文件中:https://github.com/Tencent/ncnn/blob/7886e90c65ec995159427e9e98bf9520683f661e/src/layer/sigmoid.cpp

int Sigmoid::forward_inplace(Mat& bottom_top_blob, const Option& opt) const
{
    int w = bottom_top_blob.w;
    int h = bottom_top_blob.h;
    int channels = bottom_top_blob.c;
    int size = w * h;

    #pragma omp parallel for num_threads(opt.num_threads)
    for (int q = 0; q < channels; q++)
    {
        float* ptr = bottom_top_blob.channel(q);

        for (int i = 0; i < size; i++)
        {
            float v = ptr[i];
            v = std::min(v, 88.3762626647949f);
            v = std::max(v, -88.3762626647949f);
            ptr[i] = static_cast<float>(1.f / (1.f + exp(-v)));
        }
    }

    return 0;
}

功能上是用<math.h>中的exp()实现的,为了处理溢出,ncnn专门用了这种方式去处理,我还没有见过……

            v = std::min(v, 88.3762626647949f);
            v = std::max(v, -88.3762626647949f);

不过我看一些其他地方也用了,值得学习哈!

后记

这篇博客完全是自己的个人笔记,仅供自我参考!还是强烈建议去看up主的教程! 

参考

  • https://www.jianshu.com/p/4528da002c43
  • C/C++中关于 #pragma 的深度探究_c++ #pragma_华枝歌的博客-CSDN博客
  • KuiperInfer深度学习推理框架-源码阅读和二次开发(1):算子开发流程之算子注册机制详解_沉迷单车的追风少年的博客-CSDN博客
  • C++-[override]关键字使用详解_c++ override_Planet^沐的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/454983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

音视频技术开发周刊 | 290

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 TCSVT 2022 | 基于环路多帧预测的深度视频压缩 本文基于端到端深度视频压缩框架&#xff0c;提出了一种环路多帧预测模块&#xff08;in-loop frame prediction module&a…

UV坐标应用范例——计算屏幕坐标作为UV

迷幻角色背景 大家好&#xff0c;我是阿赵。 之前介绍过了经典的Shader写法&#xff0c;物体顶点坐标在顶点程序转换到裁剪空间&#xff0c;然后在片段程序里面通过模型的UV进行贴图采样&#xff0c;然后把颜色显示在模型上面。 之前也介绍过经典的顶点程序应用&#xff0c;树木…

26.Spring-AOP(切面编程)

目录 一、Spring-AOP。 &#xff08;1&#xff09;AOP的简介。 &#xff08;2&#xff09;AOP的底层实现-动态代理。 &#xff08;2.1&#xff09;JDK的动态代理。 &#xff08;2.2&#xff09;cglib的动态代理。 &#xff08;3&#xff09;AOP的相关概念。 &#xff0…

【Linux】5、使用 Linux 快捷按键小技巧

目录 一、CTRL C二、CTRL D三、history 命令四、CTRL R五、光标移动快捷方式六、清屏 一、CTRL C &#x1f941; ① 可用于强制停止某些程序的运行 &#x1f941; ② 若命令输入错误&#xff0c;可用它退出当前命令 二、CTRL D &#x1f941; ① 退出登录的账户 &#…

WEB APIs day2

一、Dom事件基础 1.事件监听&#xff08;绑定&#xff09; 1.1 事件监听 一旦绑定后&#xff0c;这个函数不会立即执行的&#xff0c;事件什么时候触发什么时候执行 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8">…

Pyinstaller打包python文件太大?教你三个小技巧有效减小文件体积

简介 有时候需要在未安装Python环境的平台上运行py程序&#xff0c;使用pyinstaller打包很方便&#xff0c;但打包后的可执行文件实在是太大了。原因在于打包时pyinstaller本就已经关联了很多的python内联模块&#xff0c;加上我们项目中存在过多第三方类库&#xff0c;打包的…

优思学院|质量人如何利用ChatGPT提升工作效率?

在许多人知道怎么用ChatGPT之后&#xff0c;不少人开始思考如何利用这个工具来提升自己的工作效率。 质量人也不例外&#xff0c;在质量管理中&#xff0c;有许多重复的任务需要人手去完成。这些任务可能包括检查文档、审查流程、跟踪错误等。这些任务既耗费时间&#xff0c;又…

MAVEN环境变量配置(Windows 11)

1、直接在搜索框中搜&#xff1a;编辑系统环境变量 2、点击环境变量 3、 在系统变量里面新建系统变量 变量名&#xff1a;MAVEN_HOME 变量值&#xff1a;路径一定要写到maven的bin目录下 以下这种写法是错误的 4、新建系统变量完成 5、 往下滑 找到path&#xff0c;可以双击…

【Python】实战:生成无关联单选问卷 csv《跌倒风险评估量表》

目录 一、适用场景 二、业务需求 三、Python 文件 &#xff08;1&#xff09;创建文件 &#xff08;2&#xff09;代码示例 四、csv 文件 一、适用场景 实战场景&#xff1a; 问卷全部为单选题问卷问题全部为必填问题之间无关联关系每个问题的答案分数不同根据问卷全部问…

亚马逊云科技CodeWhisperer正式可用,面向个人开发者免费开放

亚马逊云科技致力于推动生成式AI技术的普惠化&#xff1a;亚马逊云科技将这些技术从研究和实验领域释放出来&#xff0c;不只是少数初创公司和资金雄厚的大型科技公司&#xff0c;而是让更多公司都能从中受益。因此&#xff0c;亚马逊云科技宣布数项创新&#xff0c;帮助客户更…

STM32-HAL-usDelay

一、STM32单片机的延时 STM32单片机的延时&#xff0c;是指在程序中暂停一段时间&#xff0c;等待一定的时间后再继续执行下一条指令。常见的延时方式有循环延时和定时器延时。 毫秒延时的使用场景&#xff1a; 等待外设完成某项操作&#xff1a;在使用外设时&#xff0c;有…

【安全与风险】总结篇

总结篇 期望学习效果学习关键点安全基础一些术语安全策略CIA 密码学概论对称vs非对称对称密码:定义非对称密码学(公钥密码学)密钥生成加密解密技术反向使用:数字签名 基础计算资源安全访问控制列表读、写、执行权限位DoS攻击 恶意软件什么是恶意软件恶意软件的类型基于主机的恶…

SpringBoot中集成任务调度

文章目录 SpringBoot中集成任务调度1. 任务调度基本介绍2. corn表达式介绍2-1 corn的每一个位置功能介绍2-2 占位符说明2-3 常用cron举例 3. SpringBoot项目中&#xff0c;集成任务调度Scheduled3-1 添加SpringBoot启动依赖3-2 具体corn任务调度计划3-3 SpringBoot启动类添加注…

java如何实现深拷贝(IT枫斗者)

java如何实现深拷贝 Java浅拷贝 浅拷贝是按位拷贝对象&#xff0c;它会创建一个新对象&#xff0c;这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型&#xff0c;拷贝的就是基本类型的值&#xff1b;如果属性是内存地址&#xff08;引用类型&#xff09;&#…

2016湖南湘潭邀请赛b题思路

最近训练时写的比赛&#xff0c;当时b题没写&#xff0c;事后补一下&#xff0c;看了下题解&#xff0c;想写下自己的解释 原题解&#xff1a;2016湖南湘潭邀请赛题解&#xff1a;2016年“长城信息”杯中国大学生程序设计比赛中南地区邀请赛&#xff08;迟来的题解&#xff09…

Koordinator 一周年,新版本 v1.2.0 支持节点资源预留,兼容社区重调度策略

作者&#xff1a;佑祎、吕风 背景 Koordinator 是一个开源项目&#xff0c;基于阿里巴巴在容器调度领域多年累积的经验孵化诞生&#xff0c;可以提升容器性能&#xff0c;降低集群资源成本。通过混部、资源画像、调度优化等技术能力&#xff0c;能够提高延迟敏感的工作负载和…

第3章:select

1.最基本的select语句 select … from…select 字段1&#xff0c;字段2&#xff0c;…from 表名* 表中所有字段&#xff08;列&#xff09; 2.列的别名 字段1 as 别名1字段1 别名1as&#xff1a;alias&#xff08;别名&#xff09;可以省略如果别名有空格使用一对””引起来…

应用于音箱领域中的音频功放IC型号推荐

音箱音频功放ic俗称“扩音机”又叫音频功率放大器IC&#xff1b;是各类音响器材中不可缺少的部分&#xff0c;其作用主要是将音源器材输入的较微弱信号进行放大后&#xff0c;产生足够大的电流去推动扬声器进行声音的重放。 现如今&#xff0c;音频功放芯片伴随着人工交互及智…

APS中零件工序间的移动方式解析

在加工装配的成批生产类型企业里&#xff0c;由于零件多种多样&#xff0c;工艺路线、加工方法和技术装备千差万别&#xff0c;因而&#xff0c;产品有多种流转方式。一般来说&#xff0c;零件在各道工序间的移动方式主要有顺序移动、平行移动和平行顺序&#xff08;平顺&#…

网络威胁情报:数据的力量

在一个日益互联和数字化的世界中&#xff0c;网络威胁已成为一项重大挑战&#xff0c;可能危及您组织的声誉、财务稳定性和整体运营效率。 事实上&#xff0c;根据 IBM 2022 年的一份报告&#xff0c;数据泄露的平均成本现在为 435 万美元。 鉴于网络威胁的重要性和影响日益突…