从零构建深度学习推理框架-4 框架中的算子注册机制
今天要讲的这一注册机制用到了设计模式中的工厂模式和单例模式,所以这节课也是对两大设计模式的一个合理应用和实践。KuiperInfer的注册表是一个map数据结构,维护了一组键值对,key是对应的OpType,用来查找对应的value,value是用于创建该层的对应方法(Creator)。我们可以看一下KuiperInfer中的Layer注册表实现:
typedef std::map<OpType, Creator> CreateRegistry;
OpType就是头文件中对应的索引:
创建该层的对应方法相当于一个工厂(Creator),Creator如下的代码所示,是一个函数指针类型,我们将存放参数的Oprator类传入到该方法中,然后该方法根据Operator内的参数返回具体的Layer.
typedef std::shared_ptr<Layer> (*Creator)(const std::shared_ptr<Operator> &op);
代表的是返回值为std::shared_ptr<Layer> 然后参数为const std::shared_ptr<Operator> &op的一类函数
只要返回值和参数的类型\个数都满足 creator就可以指向对应的函数
对应函数就是创建层layer的一个具体方法
所以目前就是这个样子:
ReluLayer定义完成--->LayerRegistererWrapper ---> RegisterCreator
接下来我们再看这个registor注册方法:
void LayerRegisterer::RegisterCreator(OpType op_type, const Creator &creator) {CHECK(creator != nullptr) << "Layer creator is empty";CreateRegistry ®istry = Registry(); //实现单例的关键// 根据operator typeCHECK_EQ(registry.count(op_type), 0) << "Layer type: " << int(op_type) << " has already registered!";// ReluLayer::CreateInstance 没有被注册过,就塞入到注册表当中registry.insert({op_type, creator});
}
先来补充一下单例模式:
单例设计编程模式
全局当中有且只有一个变量
任意次和任意一方去调用都会得到这个唯一的变量
这里的唯一变量是全局的注册表 存的时候是这个,取得时候也需要是这个
这里的Registry也写上了,就是实现单例的关键:
LayerRegisterer::CreateRegistry &LayerRegisterer::Registry() {// C++程序员高频面试点static CreateRegistry *kRegistry = new CreateRegistry();// 没有static 那就是调用一次初始化一次// 不构成单例CHECK(kRegistry != nullptr) << "Global layer register init failed!";return *kRegistry; // 返回了这个注册表
}
static CreateRegistry *kRegistry = new CreateRegistry();
这个其实只会被初始化一次
简单来说第一次,调用的时候 new CreateRegistry 存放到一个kRegistry (static)
后续调用的时候,只会返回kRegistry (static)
这是一种C++的特性。
如果没有static,那就是调用一次就初始化一次,就自然构不成单例模式。
layer_factory.cpp:
namespace kuiper_infer {
// OpType::kOperatorRelu 就是刚才还说的OpType
// ReluLayer::CreateInstance就是一个函数指针,用来初始化层的方法// 单例设计编程模式
// 全局当中有且只有一个变量
// 任意次和任意一方去调用都会得到这个唯一的变量
// 这里的唯一变量是全局的注册表 存的时候是这个,取得时候也需要是这个
// typedef std::map<OpType, Creator> CreateRegistry
// 全局当中有且只有一个 CreateRegistry 的实例
// 什么方法来控制这个变量唯一呢
void LayerRegisterer::RegisterCreator(OpType op_type, const Creator &creator) {CHECK(creator != nullptr) << "Layer creator is empty";CreateRegistry ®istry = Registry(); //实现单例的关键// 根据operator typeCHECK_EQ(registry.count(op_type), 0) << "Layer type: " << int(op_type) << " has already registered!";// ReluLayer::CreateInstance 没有被注册过,就塞入到注册表当中registry.insert({op_type, creator});
}
CreateRegistry ®istry = Registry(); //实现单例的关键
这个就是上面的Registry,之后作检查,如果已经有过的话,那count之后就该报错啦。
CHECK_EQ(registry.count(op_type), 0) << "Layer type: " << int(op_type) << " has already registered!";
没有注册机制的create
然后我们来看一下没有注册机制前的createrelu是怎么做到的:
TEST(test_layer, forward_relu1) {using namespace kuiper_infer;float thresh = 0.f;// 初始化一个relu operator 并设置属性std::shared_ptr<Operator> relu_op = std::make_shared<ReluOperator>(thresh);// 有三个值的一个tensor<float>std::shared_ptr<Tensor<float>> input = std::make_shared<Tensor<float>>(1, 1, 3);input->index(0) = -1.f; //output对应的应该是0input->index(1) = -2.f; //output对应的应该是0input->index(2) = 3.f; //output对应的应该是3// 主要第一个算子,经典又简单,我们这里开始!std::vector<std::shared_ptr<Tensor<float>>> inputs; //作为一个批次去处理std::vector<std::shared_ptr<Tensor<float>>> outputs; //放结果inputs.push_back(input);ReluLayer layer(relu_op);// 因为是4.1 所以没有作业 4.2才有
// 一个批次是1layer.Forwards(inputs, outputs);ASSERT_EQ(outputs.size(), 1);
//记得切换分支!!!!!for (int i = 0; i < outputs.size(); ++i) {ASSERT_EQ(outputs.at(i)->index(0), 0.f);ASSERT_EQ(outputs.at(i)->index(1), 0.f);ASSERT_EQ(outputs.at(i)->index(2), 3.f);}
}
有了注册机制后的createrelu:
// 有了注册机制后的框架是如何init layer
TEST(test_layer, forward_relu2) {using namespace kuiper_infer;float thresh = 0.f;std::shared_ptr<Operator> relu_op = std::make_shared<ReluOperator>(thresh);std::shared_ptr<Layer> relu_layer = LayerRegisterer::CreateLayer(relu_op);std::shared_ptr<Tensor<float>> input = std::make_shared<Tensor<float>>(1, 1, 3);input->index(0) = -1.f;input->index(1) = -2.f;input->index(2) = 3.f;std::vector<std::shared_ptr<Tensor<float>>> inputs;std::vector<std::shared_ptr<Tensor<float>>> outputs;inputs.push_back(input);relu_layer->Forwards(inputs, outputs);ASSERT_EQ(outputs.size(), 1);for (int i = 0; i < outputs.size(); ++i) {ASSERT_EQ(outputs.at(i)->index(0), 0.f);ASSERT_EQ(outputs.at(i)->index(1), 0.f);ASSERT_EQ(outputs.at(i)->index(2), 3.f);}
}
我们可以看到std::shared_ptr<Operator> relu_op = std::make_shared<ReluOperator>(thresh), 初始化了一个ReluOperator, 其中的参数为thresh=0.f.
因为我们已经在ReluLayer的实现中完成了注册,{kOperatorRelu:ReluLayer::CreateInstance} , 所以现在可以使用 LayerRegisterer::CreateLayer(relu_op) 得到我们ReluLayer中的实例化工厂方法,我们再来看看CreateLayer的实现:
std::shared_ptr<Layer> LayerRegisterer::CreateLayer(const std::shared_ptr<Operator> &op) {CreateRegistry ®istry = Registry();const OpType op_type = op->op_type_;LOG_IF(FATAL, registry.count(op_type) <= 0) << "Can not find the layer type: " << int(op_type);const auto &creator = registry.find(op_type)->second;LOG_IF(FATAL, !creator) << "Layer creator is empty!";std::shared_ptr<Layer> layer = creator(op);LOG_IF(FATAL, !layer) << "Layer init failed!";return layer;
}
可以看到传入的参数为op, 我们首先取得op中的op_type, 此处的op_type为kOperatorRelu, 根据registry.find(op_type), 就得到了层的初始化方法creator, 随后使用传入的op去初始化layer并返回实例。值得注意的是此处也调用了CreateRegistry ®istry =Registry() 返回了我们所说的全局有且唯一的Layer注册表。
此处的creator(op)就相当于调用了ReluLayer::CreateInstance.(因为:
LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance);
class LayerRegistererWrapper {public:LayerRegistererWrapper(OpType op_type, const LayerRegisterer::Creator &creator) {// 定义之后调用的// RegisterCreatorLayerRegisterer::RegisterCreator(op_type, creator);}
};
)
这样子可能看上去和上面差别不大,但是在实际应用上会便捷很多:
ops:[] = {conv 1 , conv 2 ,conv 3 ,relu ,sigmoid ,linear , conv 3};
layers = [];
for op in ops : layers.append(LayerRegisterer::CreateLayer(op))//初始化完毕
因为如果没有这个机制的话,那么语言多少层,他就要写多少层。
创建sigmoid算子:
void SigmoidLayer::Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,std::vector<std::shared_ptr<Tensor<float>>> &outputs) {CHECK(this->op_ != nullptr);CHECK(this->op_->op_type_ == OpType::kOperatorSigmoid);CHECK(!inputs.empty());const uint32_t batch_size = inputs.size();for (uint32_t i = 0; i < batch_size; ++i) {const std::shared_ptr<Tensor<float>> &input_data = inputs.at(i);std::shared_ptr<Tensor<float>> output_data = input_data->Clone();//补充,y=1/(1+e^{-x})output_data->data().transform([&](float value) {return(1/(1+exp(-1*value))); });outputs.push_back(output_data);}
}
cube的transform函数就是对于这个cube中的每一个元素进行lambda表达式中的运算
在这里预先将input_data进行了复制i,所以可以对于output中的数值进行直接的运算。
相关文章:

从零构建深度学习推理框架-4 框架中的算子注册机制
今天要讲的这一注册机制用到了设计模式中的工厂模式和单例模式,所以这节课也是对两大设计模式的一个合理应用和实践。KuiperInfer的注册表是一个map数据结构,维护了一组键值对,key是对应的OpType,用来查找对应的value,…...
使用vscode+ssh免密远程Linux
使用vscodessh免密远程Linux 使用 SSH 密钥对:使用 SSH Agent:ssh-agent的使用场景 使用 SSH 密钥对: 确保你的本地机器上已经生成了 SSH 密钥对。如果没有,请使用以下命令生成密钥对: ssh-keygen -t rsa这将在 ~/.ssh…...

rust-异步学习
rust获取future中的结果 两种主要的方法使用 async: async fn 和 async 块 async 体以及其他 future 类型是惰性的:除非它们运行起来,否则它们什么都不做。 运行 Future 最常见的方法是 .await 它。 当 .await 在 Future 上调用时,它会尝试把…...

【Azure】office365邮箱测试的邮箱账号因频繁连接邮箱服务器而被限制连接 引起邮箱显示异常
azure微软office365邮箱会对频繁连接自身邮箱服务器的IP地址进行,连接邮箱服务器IP限制,也就是黑名单,释放时间不确定,但至少一天及以上。 解决办法,换一个IP,或者新注册一个office365邮箱再重试。 以下是…...
重新登录成功和登录失败处理器
<template><div class="login"><el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form"><h3 class="title">Java1234 Vue3 后台管理系统</h3><el…...

【Spring】(三)Spring 使用注解存储和读取 Bean对象
文章目录 前言一、使用注解储存 Bean 对象1.1 配置扫描路径1.2 类注解储存 Bean 对象1.2.1 Controller(控制器存储)1.2.2 Service(服务储存)1.2.3 Repository(仓库存储)1.2.4 Component(组件储存…...

ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决
ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决 这个问题出现在使用Kyubi Spark Util处理ParallelCollectionRDD的过程中,具体是在KyubiSparkUtil.scala文件的第48行调用isEmpty方法时出现的。该问题可能是由以下几个原因引起的࿱…...

---------------- 部署 Zookeeper 集群 ----------------
部署 Zookeeper 集群 1.安装前准备2.安装 Zookeeper修改配置文件在每个节点上创建数据目录和日志目录在每个节点的dataDir指定的目录下创建一个 myid 的文件配置 Zookeeper 启动脚本 //准备 3 台服务器做 Zookeeper 集群 192.168.109.1 192.168.109.2 192.168.109.3 1.安装前准…...

SpringBoot 依赖管理和自动配置---带你了解什么是版本仲裁
😀前言 本篇博文是关于SpringBoot 依赖管理和自动配置,希望能够帮助到您😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您…...

c语言每日一练(2)
前言: 每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,暑假时三天之内必有一更,到了开学之后,将看学业情…...
代码随想录第三十七天
代码随想录第三十七天 Leetcode 738. 单调递增的数字 Leetcode 738. 单调递增的数字 题目链接: 单调递增的数字 自己的思路:完全想不到!! 正确思路:大致思路是从后向前遍历,不可以从前向后,如果从前向后没有保证单调递增的顺序&…...
Linux进程间通信--ftok
在C语言中,ftok函数用于生成一个唯一的键值,该键值通常用于创建共享内存,消息队列和信号量等系统资源的标识符。 ftok函数原型入下: key_t ftok(const char *pathname, int proj_id); 参数说明: pathname:…...

Spring Boot集成Mybatis-Plus
Spring Boot集成Mybatis-Plus 1. pom.xml导包 <!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--mysql驱动--><dependency><groupId>mysql<…...

梳理日常开发涉及的负载均衡
负载均衡是当前分布式微服务时代最能提及的词之一,出于对分层、解耦、弱依赖、可配置、可靠性等概念的解读,一对一的模式变得不再可信赖,千变万化的网络环境中,冗余和备份显得格外重要,稍大型的系统就会存在大量微服务…...

IEEE 754 浮点数运算
...
阿里巴巴Java开发手册学习记录
阿里巴巴Java开发手册学习记录 一、编程规约 1.命名风格 严禁使用英文 拼音混合使用 类名应所有单词的首字母大写,除了(UserDO,XxxDTO, XxxPo等) 常量的命名应该是大写 单词间用下划线连接 抽象类的应以Abstract/Base开头 …...

论文阅读---《Unsupervised T ransformer-Based Anomaly Detection in ECG Signals》
题目:基于Transformer的无监督心电图(ECG)信号异常检测 摘要 异常检测是数据处理中的一个基本问题,它涉及到医疗感知数据中的不同问题。技术的进步使得收集大规模和高度变异的时间序列数据变得更加容易,然而ÿ…...

收藏这8个好用的原型设计工具,轻松制作原型图
在设计工作中,原型设计是非常关键的一步,而原型设计工具又能帮助设计师更轻松地完成设计工作。今天本文将与大家分享8个好用的原型设计工具,一起来看看吧! 1、即时设计 即时设计是一个能在线协作的原型工具,也就是说…...

王道计网 第四章笔记
4.1 生活在网络层的“工人”是路由器,他负责各种异构网络的连接,但是因为他只生活在前三层所以从网络层之上的东西他不能管理,所以网路层之上的数据对于路由器来说必须是相同的、透明的。 常见的网络层协议有IP 和 ICMPTCP IP传输层协议FTP应用层协议一句话区分IP和MAC地址…...
C# Blazor 学习笔记(9):动态css/class绑定
文章目录 前言相关资料css和class绑定直接绑定间接绑定 前言 之前我们说到,我们组件化有三个目的。 不用写CSS不用写html不用写交互逻辑 为了解决第一个目的,我们需要动态css 相关资料 Blazor入手教程(二)css和class绑定 cs…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...

【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...