ch04与ch0402: 框架中算子注册机制理解

144 阅读3分钟

前言

ch04与ch0402基于算子注册机制分别实现了relu算子与sigmoid算子, 本文尝试在自己的理解下对代码进行一下分析与梳理。
项目地址
课程地址
个人完成的作业地址

代码

operater存放节点相关参数,layer负责计算执行

ops

Operator基类

enum class OpType { // op类型,每实现一个算子都要添加一个
  kOperatorUnknown = -1,
  kOperatorRelu = 0,
  kOperatorSigmoid  = 1,
};

class Operator {
 public:
  OpType op_type_ = OpType::kOperatorUnknown; //不是一个具体节点 指定为unknown

  virtual ~Operator() = default; //

  explicit Operator(OpType op_type);
};

ReluOperator类,relu算子

class ReluOperator : public Operator {
 public:
  ~ReluOperator() override = default;

  explicit ReluOperator(float thresh);

  void set_thresh(float thresh);

  float get_thresh() const;

 private:
  // 需要传递到reluLayer中,怎么传递?
  float thresh_ = 0.f; // 用于过滤tensor<float>值当中大于thresh的部分
};

layer

layer基类,只包括层名layer_name

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

      virtual void Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                      std::vector<std::shared_ptr<Tensor<float>>> &outputs);
      // reluLayer中 inputs 等于 x , outputs 等于 y= x,if x>0
      // 计算得到的结果放在y当中,x是输入,放在inputs中
      virtual ~Layer() = default;
  private:
      std::string layer_name_; //relu layer "relu"
};

ReluLayer类

class ReluLayer : public Layer {
 public:
  ~ReluLayer() override = default;

  // 通过这里,把relu_op中的thresh告知给relu layer, 因为计算的时候要用到
  explicit ReluLayer(const std::shared_ptr<Operator> &op);

  // 执行relu 操作的具体函数Forwards
  void Forwards(const std::vector<std::shared_ptr<Tensor<float>>> &inputs,
                std::vector<std::shared_ptr<Tensor<float>>> &outputs) override;
  
  // 返回一个指向relu_layer的智能指针,其中具体参数由op给出
  static std::shared_ptr<Layer> CreateInstance(const std::shared_ptr<Operator> &op);

 private:
 // 负责存放计算需要用到的参数
  std::unique_ptr<ReluOperator> op_;
};

// 重要
// ReluLayer定义完成自动调用
// 只执行一次, 不管这个类被初始化几次, 这句话只执行一次(应该是编译时候)
LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance);

factory

class LayerRegisterer {
 public:
  typedef std::shared_ptr<Layer> (*Creator)(const std::shared_ptr<Operator> &op);
  // 函数指针类型,我们将存放参数的Oprator类传入到该方法中,然后该方法根据Operator内的参数返回具体的Layer.

  typedef std::map<OpType, Creator> CreateRegistry;
  // value是用于创建该层的对应方法(Creator)

  // 往字典注册op算子
  static void RegisterCreator(OpType op_type, const Creator &creator);

  // 根据字典和op创建layer
  static std::shared_ptr<Layer> CreateLayer(const std::shared_ptr<Operator> &op);
  
  // 返回一个字典
  static CreateRegistry &Registry();
};

class LayerRegistererWrapper {
 public:
  //op_type是算子的类型,作为Layer注册表的key使用,creator是创建具体层的工厂方法,作为Layer注册表的value
  LayerRegistererWrapper(OpType op_type, const LayerRegisterer::Creator &creator) {
    LayerRegisterer::RegisterCreator(op_type, creator);
  }
};

把注册相关函数一次抽出来

// 定义完类 作为类的一部分自动运行 构造完自动析构
LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance);

// 调用注册函数
LayerRegistererWrapper(OpType op_type, const LayerRegisterer::Creator &creator) {
    LayerRegisterer::RegisterCreator(op_type, creator);
  }
  
LayerRegisterer::RegisterCreator(OpType op_type, const Creator& creator)

在RegisterCreator注册时, 还要调用Registry()返回注册表 --->存入实现方法

调用方法

// 可以不使用注册机制
std::shared_ptr<Operator> relu_op = std::make_shared<ReluOperator>(thresh);
ReluLayer layer(relu_op);

// 工厂模式调用
std::shared_ptr<Operator> relu_op = std::make_shared<ReluOperator>(thresh);
std::shared_ptr<Layer> relu_layer = LayerRegisterer::CreateLayer(relu_op);

后记

这节最困惑的是, kReluLayer和kSigmoidLayer何时执行(被定义)。

LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance);
LayerRegistererWrapper kSigmoidLayer(OpType::kOperatorSigmoid, SigmoidLayer::CreateInstance);

为什么要有LayerRegistererWrapper函数, 毕竟只调用了一下RegisterCreator函数。
CreateInstance有什么用,返回一个对象指针,为什么不能直接返回。
可能是注册表值里面是一个函数指针,所以需要CreateInstance?
LayerRegistererWrapper应该保证kReluLayer是一个对象,然后会自动销毁,如果是函数有可能被多次调用。
kReluLayer和kSigmoidLayer应该是函数被定义就执行完毕,在main之前?

TEST

算子test成功 4.1.png

目前总共通过10个test 4.2.png