<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自制深度學(xué)習(xí)推理框架-第五課-起飛!框架中的算子注冊機制

          共 9385字,需瀏覽 19分鐘

           ·

          2023-01-09 15:35

          我們的課程主頁

          https://github.com/zjhellofss/KuiperInfer 歡迎pr和點贊

          為什么要有注冊機制

          KuiperInfer內(nèi)部維護(hù)了一個注冊表,用于查找Layer對應(yīng)的初始化函數(shù)。這里的Layer是KuiperInfer中的算子具體執(zhí)行者,例如我們在上一節(jié)課中我們講過的ReluLayer,用于具體執(zhí)行Relu方法,我們這里回顧一下Layer的定義:

          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)
          ;

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

          Layer注冊機制的實現(xiàn)

          注冊機制的原理

          今天要講的這一注冊機制用到了設(shè)計模式中的工廠模式和單例模式,所以這節(jié)課也是對兩大設(shè)計模式的一個合理應(yīng)用和實踐。KuiperInfer的注冊表是一個map數(shù)據(jù)結(jié)構(gòu),維護(hù)了一組鍵值對,key是對應(yīng)的OpType,用來查找對應(yīng)的value,value是用于創(chuàng)建該層的對應(yīng)方法(Creator)。我們可以看一下KuiperInfer中的Layer注冊表實現(xiàn):

           typedef std::map<OpType, Creator> CreateRegistry;

          創(chuàng)建該層的對應(yīng)方法相當(dāng)于一個工廠(Creator),Creator如下的代碼所示,是一個函數(shù)指針類型,我們將存放參數(shù)的Oprator類傳入到該方法中,然后該方法根據(jù)Operator內(nèi)的參數(shù)返回具體的Layer.

          typedef std::shared_ptr<Layer> (*Creator)(const std::shared_ptr<Operator> &op);

          向注冊表中注冊Layer

          如果我們將Layer的注冊機制當(dāng)成一個注冊表的話,那么就會有存入的階段也有取出的階段什么時候?qū)ayer的注冊機制存入到注冊表呢?以如下的代碼ReluLayer作為一個例子:

          ReluLayer::ReluLayer(const std::shared_ptr<Operator> &op) : Layer("Relu") {
            CHECK(op->op_type_ == OpType::kOperatorRelu) << "Operator has a wrong type: " << int(op->op_type_);
            ReluOperator *relu_op = dynamic_cast<ReluOperator *>(op.get());
            CHECK(relu_op != nullptr) << "Relu operator is empty";
            this->op_ = std::make_unique<ReluOperator>(relu_op->get_thresh());
          }

          void ReluLayer::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::kOperatorRelu);
            const uint32_t batch_size = inputs.size();
            for (int i = 0; i < batch_size; ++i) {
              CHECK(!inputs.at(i)->empty());
              const std::shared_ptr<Tensor<float>> &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<Layer> ReluLayer::CreateInstance(const std::shared_ptr<Operator> &op) {
            std::shared_ptr<Layer> relu_layer = std::make_shared<ReluLayer>(op);
            return relu_layer;
          }

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

          LayerRegistererWrapper kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance), 完成了ReluLayer定義后的注冊,其中key為kOperatorRelu, value 為ReluLayer::CreateInstance. CreateInstance是一個具體的工廠方法,用來在之后對ReluLayer進(jìn)行初始化,我們接下來看看這里注冊機制的具體實現(xiàn):

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

          LayerRegistererWrapper是一個類

          我們在調(diào)用kReluLayer(OpType::kOperatorRelu, ReluLayer::CreateInstance)的時候,LayerRegistererWrapper的構(gòu)造方法再調(diào)用了RegisterCreator。所以到目前為止,調(diào)用鏈?zhǔn)沁@樣的:

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

          接下來看看RegisterCreator這個注冊方法的實現(xiàn):

          void LayerRegisterer::RegisterCreator(OpType op_type, const Creator &creator) {
            CHECK(creator != nullptr) << "Layer creator is empty";
            CreateRegistry &registry = Registry();
            CHECK_EQ(registry.count(op_type), 0) << "Layer type: " << int(op_type) << " has already registered!";
            registry.insert({op_type, creator});
          }

          再強調(diào)一遍,方法中的op_type是算子的類型,作為Layer注冊表的key使用,creator是創(chuàng)建具體層的工廠方法,作為Layer注冊表的value. 目前為止,調(diào)用鏈?zhǔn)沁@樣的:

          ReluLayer定義完成 --->LayerRegistererWrapper ---> RegisterCreator --->Registry返回注冊表 --->存入實現(xiàn)方法

          單例注冊表的實現(xiàn)

          可以看到CreateRegistry &registry =Registry() 這里返回注冊表的實例,此處的Layer注冊表是全局唯一的,全局唯一的實現(xiàn)方法是單例設(shè)計模式的應(yīng)用,我們看一下下方的具體實現(xiàn):

          LayerRegisterer::CreateRegistry &LayerRegisterer::Registry() {
            static CreateRegistry *kRegistry = new CreateRegistry();
            CHECK(kRegistry != nullptr) << "Global layer register init failed!";
            return *kRegistry;
          }

          此處的static CreateRegistry *kRegistry =newCreateRegistry() , 使得Layer注冊表在全局有且只有一個,無論我們調(diào)用了多少次Registry(), 該方法都會返回同一個Layer注冊表。

          向注冊表中取出Layer

          在完成Layer注冊之后,我們就可以根據(jù)OpType來取出用于實例化一個Layer的工廠函數(shù),比如上面我們完成了ReluLayer的注冊后,系統(tǒng)中的Layer注冊表中是這樣的:

          {kOperatorRelu:ReluLayer::CreateInstance}

          我們在需要的時候時候根據(jù)kOperator來取出ReluLayer::CreateInstance, 我們再以Relu中的工廠函數(shù)為例子看看一個具體工廠函數(shù)的實現(xiàn):

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

          可以看到這里的工廠函數(shù)比較簡單,直接傳入ReluOperator完成對ReluLayer的初始化。

          一個例子

          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>>(113);
            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, 其中的參數(shù)為thresh=0.f.

          因為我們已經(jīng)在ReluLayer的實現(xiàn)中完成了注冊,{kOperatorRelu:ReluLayer::CreateInstance} , 所以現(xiàn)在可以使用LayerRegisterer::CreateLayer(relu_op)得到我們ReluLayer中的實例化工廠方法,我們再來看看CreateLayer的實現(xiàn):

          std::shared_ptr<Layer> LayerRegisterer::CreateLayer(const std::shared_ptr<Operator> &op) {
            CreateRegistry &registry = 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;
          }

          可以看到傳入的參數(shù)為op, 我們首先取得op中的op_type, 此處的op_type為kOperatorRelu, 根據(jù)registry.find(op_type), 就得到了層的初始化方法creator, 隨后使用傳入的op去初始化layer并返回實例。值得注意的是此處也調(diào)用了CreateRegistry &registry =Registry()返回了我們所說的全局有且唯一的Layer注冊表. 此處的creator(op)就相當(dāng)于調(diào)用了ReluLayer::CreateInstance.

          如何使用我們已經(jīng)實現(xiàn)并注冊的算子

          請看視頻部分

          本節(jié)課的作業(yè)

          作業(yè)地址

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

          • git checkout fouthtwo

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

          具體步驟

          請實現(xiàn)一個sigmoid算子, sigmoid的算子由如下的公式定義:

          請你以Relu Layer相同的辦法, 去實現(xiàn)并注冊這個算子并完成test_sigmoid.cpp下TEST(test_layer, forward_sigmoid) 測試用例的測試. 代碼的整體部分已經(jīng)給出, 繼續(xù)完成核心部分即可.

          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  丁香色网站 | 久久成人视屏 | 国产jk在线观看 | 亚洲日韩东京热 | 色丁香五月激情 |