从零构建深度学习推理框架-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…...
适合地产人用的中介房源管理系统
在房产经纪行业,房源管理与客源管理是经纪人日常工作的核心,直接影响业务效率与成交转化。选择一套适配行业需求的中介房源管理系统,能帮助中介团队规范流程、降低运营成本、大幅提升业绩。今天我们以客观视角,详细解析全房源系统…...
PlayAI语音合成质量到底如何?12款竞品横向对比+5项MOS/LSD/STOI硬指标揭榜
更多请点击: https://kaifayun.com 第一章:PlayAI语音合成质量评测报告 PlayAI 是一款面向开发者与内容创作者的实时语音合成(TTS)服务,支持多语种、多音色及情感可控输出。本报告基于客观可复现的评测流程࿰…...
ROS Noetic实战:从bag包里‘抠’出雷达点云和IMU数据的保姆级教程(Ubuntu 20.04)
ROS Noetic实战:从bag包里提取雷达点云和IMU数据的完整指南(Ubuntu 20.04)在机器人开发中,ROS bag文件就像是一个装满珍贵数据的宝箱,而雷达点云和IMU数据则是其中最闪亮的宝石。作为一名长期与ROS打交道的开发者&…...
荣耀出征官方网站下载正版手游 翅膀养成细节玩法全方位讲解
玩荣耀出征的玩家都清楚,翅膀不仅是角色的颜值象征,更是提升整体战力的核心途径。很多新手玩家只顾着升级、刷装备,完全忽略翅膀养成,导致等级很高但战力始终上不去。还有不少玩家胡乱合成、盲目进阶,浪费了大量稀有翅…...
Claude端到端测试设计:从零搭建可审计、可回放、可量化的AI服务测试流水线(含开源Schema校验工具)
更多请点击: https://codechina.net 第一章:Claude端到端测试设计 端到端测试是验证Claude模型在真实用户交互链路中行为一致性的关键手段。它覆盖从原始提示输入、上下文管理、流式响应生成,到输出解析与业务校验的全路径,确保模…...
论文写作效率翻倍?okbiye 毕业论文 AI 功能全解析:从需求到终稿的规范路径
okbiye-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPT毕业论文 - Okbiye智能写作https://www.okbiye.com/ai/bylw 一、从界面看本质:okbiye 毕业论文 AI 写作的设计逻辑 打开 okbiye 的毕业论文 AI 写作页面,首先能感受到的是清晰的…...
从开题到定稿零焦虑:okbiye AI 论文写作,帮你把毕业季的 “大山” 变成坦途
okbiye-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPT毕业论文 - Okbiye智能写作https://www.okbiye.com/ai/bylw 毕业季的深夜,宿舍台灯下的屏幕亮着刺眼的光,文档里的字数停留在三位数,而 deadline 正一天天逼近。你是…...
高精度光照检测
光线检测仪,kotlin开发,调用手机感光模块检测室内外光照强度,用途多多,我主要用途孩子写作业检测光照保护视力。 食用方法∶打开即测,速度快,无广告,手机平视即可,无须直视光线。 买…...
使用Taotoken CLI工具一键配置多开发环境下的统一模型接入点
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Taotoken CLI工具一键配置多开发环境下的统一模型接入点 在团队协作或管理多个AI应用项目时,一个常见的痛点是每个…...
为Claude Code配置Taotoken解决账号封禁与Token不足难题
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 为Claude Code配置Taotoken解决账号封禁与Token不足难题 对于依赖Claude Code进行日常编程辅助的开发者而言,直接使用官…...
