社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  机器学习算法

自制深度学习推理框架-第五课-起飞!框架中的算子注册机制

GiantPandaCV • 2 年前 • 603 次点击  

我们的课程主页

https://github.com/zjhellofss/KuiperInfer 欢迎pr和点赞

为什么要有注册机制

KuiperInfer内部维护了一个注册表,用于查找Layer对应的初始化函数。这里的Layer是KuiperInfer中的算子具体执行者,例如我们在上一节课中我们讲过的ReluLayer,用于具体执行Relu方法,我们这里回顾一下Layer的定义:

class Layer {
 public:
  explicit Layer(const std::string &layer_name);

  virtual void Forwards(const std::vector<std::shared_ptrfloat>>> &inputs,
                        std::vector<std::shared_ptrfloat
>>> &outputs);

  virtual ~Layer() = default;
 private:
  std::string layer_name_; 
};

Layer注册机制的实现

注册机制的原理

今天要讲的这一注册机制用到了设计模式中的工厂模式和单例模式,所以这节课也是对两大设计模式的一个合理应用和实践。KuiperInfer的注册表是一个map数据结构,维护了一组键值对,key是对应的OpType,用来查找对应的value,value是用于创建该层的对应方法(Creator)。我们可以看一下KuiperInfer中的Layer注册表实现:

 typedef std::map CreateRegistry;

创建该层的对应方法相当于一个工厂(Creator),Creator如下的代码所示,是一个函数指针类型,我们将存放参数的Oprator类传入到该方法中,然后该方法根据Operator内的参数返回具体的Layer.

typedef std::shared_ptr (*Creator)(const std::shared_ptr &op);

向注册表中注册Layer

如果我们将Layer的注册机制当成一个注册表的话,那么就会有存入的阶段也有取出的阶段什么时候将Layer的注册机制存入到注册表呢?以如下的代码ReluLayer作为一个例子:

ReluLayer::ReluLayer(const std


    
::shared_ptr &op) : Layer("Relu") {
  CHECK(op->op_type_ == OpType::kOperatorRelu) op_type_);
  ReluOperator *relu_op = dynamic_cast(op.get());
  CHECK(relu_op != nullptr)   this->op_ = std::make_unique(relu_op->get_thresh());
}

void ReluLayer::Forwards(const std::vector<std::shared_ptrfloat>>> &inputs,
                         std::vector<std::shared_ptrfloat
>>> &outputs) {

  CHECK(this->op_ != nullptr);
  CHECK(this->op_->op_type_ == OpType::kOperatorRelu);
  const uint32_t batch_size = inputs.size();
  for (int i = 0; i     CHECK(!inputs.at(i)->empty());
    const std::shared_ptrfloat>> &input_data = inputs.at(i); 
    input_data->data().transform([&](float value) {
      float thresh = op_->get_thresh();
      if (value >= thresh) {
        return value; 
      } else {
        return 0.f;
      }
    });
    outputs.push_back(input_data);
  }
}

std::shared_ptr ReluLayer::CreateInstance(const std::shared_ptr &op) {
  std::shared_ptr relu_layer = std::make_shared(op);
  return relu_layer;
}

LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance);

LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance), 完成了ReluLayer定义后的注册,其中key为kOperatorRelu, value 为ReluLayer::CreateInstance. CreateInstance是一个具体的工厂方法,用来在之后对ReluLayer进行初始化,我们接下来看看这里注册机制的具体实现:

class LayerRegistererWrapper {
 public:
  LayerRegistererWrapper(OpType op_type, const LayerRegisterer::Creator &creator) {
    LayerRegisterer::RegisterCreator(op_type, creator);
  }
};

LayerRegistererWrapper是一个类

我们在调用kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance)的时候,LayerRegistererWrapper的构造方法再调用了RegisterCreator。所以到目前为止,调用链是这样的:

ReluLayer定义完成--->LayerRegistererWrapper ---> RegisterCreator

接下来看看RegisterCreator这个注册方法的实现:

void LayerRegisterer::RegisterCreator(OpType op_type, const Creator &creator) {
  CHECK(creator != nullptr)   CreateRegistry &registry = Registry();
  CHECK_EQ(registry.count(op_type), 0)   registry.insert({op_type, creator});
}

再强调一遍,方法中的op_type是算子的类型,作为Layer注册表的key使用,creator是创建具体层的工厂方法,作为Layer注册表的value. 目前为止,调用链是这样的:




    
ReluLayer定义完成 --->LayerRegistererWrapper ---> RegisterCreator --->Registry返回注册表 --->存入实现方法

单例注册表的实现

可以看到CreateRegistry &registry =Registry() 这里返回注册表的实例,此处的Layer注册表是全局唯一的,全局唯一的实现方法是单例设计模式的应用,我们看一下下方的具体实现:

LayerRegisterer::CreateRegistry &LayerRegisterer::Registry() {
  static CreateRegistry *kRegistry = new CreateRegistry();
  CHECK(kRegistry != nullptr)   return *kRegistry;
}

此处的static CreateRegistry *kRegistry =newCreateRegistry() , 使得Layer注册表在全局有且只有一个,无论我们调用了多少次Registry(), 该方法都会返回同一个Layer注册表。

向注册表中取出Layer

在完成Layer注册之后,我们就可以根据OpType来取出用于实例化一个Layer的工厂函数,比如上面我们完成了ReluLayer的注册后,系统中的Layer注册表中是这样的:

{kOperatorRelu:ReluLayer::CreateInstance}

我们在需要的时候时候根据kOperator来取出ReluLayer::CreateInstance, 我们再以Relu中的工厂函数为例子看看一个具体工厂函数的实现:

std::shared_ptr ReluLayer::CreateInstance(const std::shared_ptr &op) {
  std::shared_ptr relu_layer = std::make_shared(op);
  return relu_layer;
}

可以看到这里的工厂函数比较简单,直接传入ReluOperator完成对ReluLayer的初始化。

一个例子

TEST(test_layer, forward_relu2) {
  using namespace kuiper_infer;
  float thresh = 0.f;
  std::shared_ptr relu_op = std::make_shared(thresh);
  std::shared_ptr relu_layer = LayerRegisterer::CreateLayer(relu_op);

  std::shared_ptrfloat>> input = std::make_sharedfloat>>(113);
  input->index(0) = -1.f;
  input->index(1) = -2.f;
  input->index(2) = 3.f;
  std::vector<std::shared_ptrfloat>>> inputs;
  std::vector<std::shared_ptrfloat>>> outputs;
  inputs.push_back(input);
  relu_layer->Forwards(inputs, outputs);
  ASSERT_EQ(outputs.size(), 1);
  for (int i = 0; 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 relu_op = std::make_shared(thresh), 初始化了一个ReluOperator, 其中的参数为thresh=0.f.

因为我们已经在ReluLayer的实现中完成了注册,{kOperatorRelu:ReluLayer::CreateInstance} , 所以现在可以使用LayerRegisterer::CreateLayer(relu_op)得到我们ReluLayer中的实例化工厂方法,我们再来看看CreateLayer的实现:

std::shared_ptr LayerRegisterer::CreateLayer(const std::shared_ptr &op) {
  CreateRegistry &registry = Registry();
  const OpType op_type = op->op_type_;

  LOG_IF(FATAL, registry.count(op_type) <= 0)   const auto &creator = registry.find(op_type)->second;

  LOG_IF(FATAL, !creator)   std::shared_ptr layer = creator(op);
  LOG_IF(FATAL, !layer)   return layer;
}

可以看到传入的参数为op, 我们首先取得op中的op_type, 此处的op_type为kOperatorRelu, 根据registry.find(op_type), 就得到了层的初始化方法creator, 随后使用传入的op去初始化layer并返回实例。值得注意的是此处也调用了CreateRegistry &registry =Registry()返回了我们所说的全局有且唯一的Layer注册表. 此处的creator(op)就相当于调用了ReluLayer::CreateInstance.

如何使用我们已经实现并注册的算子

请看视频部分

本节课的作业

作业地址

  • git clone https://github.com/zjhellofss/KuiperCourse/tree/fouthtwo/source/layer

  • git checkout fouthtwo

  • 备用地址: https://gitee.com/fssssss/KuiperCourse/

具体步骤

请实现一个sigmoid算子, sigmoid的算子由如下的公式定义:

请你以Relu Layer相同的办法, 去实现并注册这个算子并完成test_sigmoid.cpp下TEST(test_layer, forward_sigmoid) 测试用例的测试. 代码的整体部分已经给出, 继续完成核心部分即可.

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/151252
 
603 次点击