實作自訂委派

TensorFlow Lite Delegate 可讓您在其他執行程式上執行模型 (部分或整個)。此機制可利用各種裝置端加速器進行推論,例如 GPU 或 Edge TPU (Tensor Processing Unit)。這可讓開發人員享有從預設的 TFLite 彈性的分離方法,以加快推論速度。

下圖為委派代表摘要說明,詳情請見後續章節。

TFLite 委派代表

何時該建立自訂委派代表?

TensorFlow Lite 有各種目標加速器 (例如 GPU、DSP 和 EdgeTPU) 委派項目。

在下列情況中,建立自己的委派代表非常實用:

  • 您想要整合任何現有委派代表不支援的新機器學習推論引擎。
  • 您有一個自訂硬體加速器,用來改善已知情境的執行階段。
  • 您開發的 CPU 最佳化作業 (例如運算子融合) 可以加快特定模型的速度。

委派代表如何運作?

我們以簡單的模型圖為例,以及一個委派的「MyDelegate」以加快 Conv2D 和 Mean 作業的實作速度。

原始圖表

套用這個「MyDelegate」後,原始 TensorFlow Lite 圖表的更新方式如下:

委派代表的圖表

TensorFlow Lite 根據以下兩項規則分割原始圖形時,會取得上述圖表:

  • 委派可處理的特定作業會進入分區,但仍滿足作業之間原始運算工作流程的依附元件。
  • 每個待委派的分區只包含不是由委派處理的輸入和輸出節點。

委派處理的每個分區都會由委派節點取代 (也可稱為委派核心),此種圖表會依照叫用呼叫評估分區。

視模型而定,最終圖形可能會有一或多個節點,而後者代表委派不支援部分運算。一般而言,您不會希望委派代表處理多個分區,因為每次您從委派代表切換至主要圖形時,將委派子圖表的結果傳遞至主要圖形,會因記憶體複製 (例如 GPU 到 CPU) 而產生負擔。這類負擔可能會抵銷效能提升,尤其是在出現大量記憶體副本時。

導入自己的自訂委派代表

我們建議您使用 SimpleDelegate API 新增委派代表。

如要建立新的委派,您必須實作 2 個介面,並為介面方法提供自己的實作項目。

1 - SimpleDelegateInterface

這個類別代表委派項目的功能、支援哪些作業,以及用於建立核心 (封裝委派圖表) 的工廠類別。詳情請參閱這個 C++ 標頭檔案中定義的介面。程式碼中的註解會詳細說明每個 API。

2 - SimpleDelegateKernelInterface

這個類別會封裝初始化 / 準備 / 執行委派分區的邏輯。

包含:(請參閱定義)

  • Init(...):系統會呼叫一次以執行任何一次性初始化。
  • Prepare(...):針對這個節點的每個不同執行個體呼叫 - 如果您有多個委派分區,就會發生此情況。通常您會想要在此執行記憶體配置,因為每次調整張量時,系統都會呼叫此方法。
  • Invoke(...):系統會呼叫此方法進行推論。

範例

在此範例中,您將建立非常簡單的委派,以便僅支援 2 種類型的作業 (ADD) 和 (SUB),且僅支援 float32 張量。

// MyDelegate implements the interface of SimpleDelegateInterface.
// This holds the Delegate capabilities.
class MyDelegate : public SimpleDelegateInterface {
 public:
  bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
                                 const TfLiteNode* node,
                                 TfLiteContext* context) const override {
    // Only supports Add and Sub ops.
    if (kTfLiteBuiltinAdd != registration->builtin_code &&
        kTfLiteBuiltinSub != registration->builtin_code)
      return false;
    // This delegate only supports float32 types.
    for (int i = 0; i < node->inputs->size; ++i) {
      auto& tensor = context->tensors[node->inputs->data[i]];
      if (tensor.type != kTfLiteFloat32) return false;
    }
    return true;
  }

  TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }

  const char* Name() const override {
    static constexpr char kName[] = "MyDelegate";
    return kName;
  }

  std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
      override {
    return std::make_unique<MyDelegateKernel>();
  }
};

接著,請從 SimpleDelegateKernelInterface 繼承,建立自己的委派核心

// My delegate kernel.
class MyDelegateKernel : public SimpleDelegateKernelInterface {
 public:
  TfLiteStatus Init(TfLiteContext* context,
                    const TfLiteDelegateParams* params) override {
    // Save index to all nodes which are part of this delegate.
    inputs_.resize(params->nodes_to_replace->size);
    outputs_.resize(params->nodes_to_replace->size);
    builtin_code_.resize(params->nodes_to_replace->size);
    for (int i = 0; i < params->nodes_to_replace->size; ++i) {
      const int node_index = params->nodes_to_replace->data[i];
      // Get this node information.
      TfLiteNode* delegated_node = nullptr;
      TfLiteRegistration* delegated_node_registration = nullptr;
      TF_LITE_ENSURE_EQ(
          context,
          context->GetNodeAndRegistration(context, node_index, &delegated_node,
                                          &delegated_node_registration),
          kTfLiteOk);
      inputs_[i].push_back(delegated_node->inputs->data[0]);
      inputs_[i].push_back(delegated_node->inputs->data[1]);
      outputs_[i].push_back(delegated_node->outputs->data[0]);
      builtin_code_[i] = delegated_node_registration->builtin_code;
    }
    return kTfLiteOk;
  }

  TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) override {
    return kTfLiteOk;
  }

  TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) override {
    // Evaluate the delegated graph.
    // Here we loop over all the delegated nodes.
    // We know that all the nodes are either ADD or SUB operations and the
    // number of nodes equals ''inputs_.size()'' and inputs[i] is a list of
    // tensor indices for inputs to node ''i'', while outputs_[i] is the list of
    // outputs for node
    // ''i''. Note, that it is intentional we have simple implementation as this
    // is for demonstration.

    for (int i = 0; i < inputs_.size(); ++i) {
      // Get the node input tensors.
      // Add/Sub operation accepts 2 inputs.
      auto& input_tensor_1 = context->tensors[inputs_[i][0]];
      auto& input_tensor_2 = context->tensors[inputs_[i][1]];
      auto& output_tensor = context->tensors[outputs_[i][0]];
      TF_LITE_ENSURE_EQ(
          context,
          ComputeResult(context, builtin_code_[i], &input_tensor_1,
                        &input_tensor_2, &output_tensor),
          kTfLiteOk);
    }
    return kTfLiteOk;
  }

 private:
  // Computes the result of addition of 'input_tensor_1' and 'input_tensor_2'
  // and store the result in 'output_tensor'.
  TfLiteStatus ComputeResult(TfLiteContext* context, int builtin_code,
                             const TfLiteTensor* input_tensor_1,
                             const TfLiteTensor* input_tensor_2,
                             TfLiteTensor* output_tensor) {
    if (NumElements(input_tensor_1) != NumElements(input_tensor_2) ||
        NumElements(input_tensor_1) != NumElements(output_tensor)) {
      return kTfLiteDelegateError;
    }
    // This code assumes no activation, and no broadcasting needed (both inputs
    // have the same size).
    auto* input_1 = GetTensorData<float>(input_tensor_1);
    auto* input_2 = GetTensorData<float>(input_tensor_2);
    auto* output = GetTensorData<float>(output_tensor);
    for (int i = 0; i < NumElements(input_tensor_1); ++i) {
      if (builtin_code == kTfLiteBuiltinAdd)
        output[i] = input_1[i] + input_2[i];
      else
        output[i] = input_1[i] - input_2[i];
    }
    return kTfLiteOk;
  }

  // Holds the indices of the input/output tensors.
  // inputs_[i] is list of all input tensors to node at index 'i'.
  // outputs_[i] is list of all output tensors to node at index 'i'.
  std::vector<std::vector<int>> inputs_, outputs_;
  // Holds the builtin code of the ops.
  // builtin_code_[i] is the type of node at index 'i'
  std::vector<int> builtin_code_;
};


對新的委派項目進行基準測試並評估

TFLite 提供一組工具,可讓您快速針對 TFLite 模型進行測試。

  • 模型基準工具:此工具會使用 TFLite 模型,產生隨機輸入,然後重複執行模型以指定執行次數。並在結尾輸出匯總延遲統計資料。
  • 推論差異工具:針對特定模型,這項工具會產生隨機的高斯文資料,並透過兩個不同的 TFLite 解譯器傳遞資料,一個執行單一執行緒 CPU 核心,另一個則使用使用者定義的規格進行傳遞。它會按元素測量每個解譯器之間的絕對差異。這項工具也有助於偵錯準確度問題。
  • 此外,您也可以使用圖片分類和物件偵測等工作專用的評估工具。您可以在這裡找到這些工具

此外,TFLite 還有一組核心與運算單元測試,可用來重複使用更多涵蓋率資料來測試新的委派項目,並確保一般 TFLite 執行路徑未損毀。

如要重複為新委派代表執行 TFLite 測試及工具,您可以使用以下兩個選項中的任一項:

選擇最佳方法

這兩種方式都需要一些調整,詳情如下。不過,第一種方法會以靜態方式連結委派項目,且需要重新建構測試、基準化和評估工具。相對地,第二項動作會將委派項目設為共用程式庫,並需要您公開共用資料庫中建立/刪除方法。

因此,外部委派機制就能與 TFLite 預先建構的 Tensorflow Lite 工具二進位檔搭配使用。但這個做法較不明確,在自動化整合測試中可能比較難設定。使用委派註冊商做法更清楚瞭解所在位置。

方法 1:使用委派註冊商

委派註冊商會保存一份委派提供者清單,每個供應商都可以根據指令列標記輕鬆建立 TFLite 委派項目,因此對工具而言很方便。如要插入上述所有 Tensorflow Lite 工具的新委派項目,請先建立新的委派提供者,然後只對 BUILD 規則進行幾項變更。以下為這項整合程序的完整範例 (您可以在這裡找到程式碼)。

假設您擁有實作 SimpleDelegate API 的委派,以及用於建立/刪除此「虛擬」委派代表的臨時「C」 API,如下所示:

// Returns default options for DummyDelegate.
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault();

// Creates a new delegate instance that need to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options);

// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate);

如要整合「DummyDelegate」與 Benchmark Tool 和 Inference Tool,請按照下列方式定義 DelegateProvider:

class DummyDelegateProvider : public DelegateProvider {
 public:
  DummyDelegateProvider() {
    default_params_.AddParam("use_dummy_delegate",
                             ToolParam::Create<bool>(false));
  }

  std::vector<Flag> CreateFlags(ToolParams* params) const final;

  void LogParams(const ToolParams& params) const final;

  TfLiteDelegatePtr CreateTfLiteDelegate(const ToolParams& params) const final;

  std::string GetName() const final { return "DummyDelegate"; }
};
REGISTER_DELEGATE_PROVIDER(DummyDelegateProvider);

std::vector<Flag> DummyDelegateProvider::CreateFlags(ToolParams* params) const {
  std::vector<Flag> flags = {CreateFlag<bool>("use_dummy_delegate", params,
                                              "use the dummy delegate.")};
  return flags;
}

void DummyDelegateProvider::LogParams(const ToolParams& params) const {
  TFLITE_LOG(INFO) << "Use dummy test delegate : ["
                   << params.Get<bool>("use_dummy_delegate") << "]";
}

TfLiteDelegatePtr DummyDelegateProvider::CreateTfLiteDelegate(
    const ToolParams& params) const {
  if (params.Get<bool>("use_dummy_delegate")) {
    auto default_options = TfLiteDummyDelegateOptionsDefault();
    return TfLiteDummyDelegateCreateUnique(&default_options);
  }
  return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}

BUILD 規則定義相當重要,因為您需要確保程式庫一律連結,且不會由最佳化器捨棄。

#### The following are for using the dummy test delegate in TFLite tooling ####
cc_library(
    name = "dummy_delegate_provider",
    srcs = ["dummy_delegate_provider.cc"],
    copts = tflite_copts(),
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/tools/delegates:delegate_provider_hdr",
    ],
    alwayslink = 1, # This is required so the optimizer doesn't optimize the library away.
)

現在,請在 BUILD 檔案中新增這兩個包裝函式規則,即可建立基準工具/推論工具版本,以及其他可透過自己的委派執行的評估工具。

cc_binary(
    name = "benchmark_model_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/benchmark:benchmark_model_main",
    ],
)

cc_binary(
    name = "inference_diff_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/inference_diff:run_eval_lib",
    ],
)

cc_binary(
    name = "imagenet_classification_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/imagenet_image_classification:run_eval_lib",
    ],
)

cc_binary(
    name = "coco_object_detection_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/coco_object_detection:run_eval_lib",
    ],
)

您也可以按照這裡的說明,將這個委派提供者新增至 TFLite 核心測試。

選項 2:使用外部委派代表

在此替代方案中,您必須先建立外部委派轉接器 external_delegate_adaptor.cc,如下所示。請注意,與先前提到的方法 1 相比,這種做法較不合宜。

TfLiteDelegate* CreateDummyDelegateFromOptions(char** options_keys,
                                               char** options_values,
                                               size_t num_options) {
  DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();

  // Parse key-values options to DummyDelegateOptions.
  // You can achieve this by mimicking them as command-line flags.
  std::unique_ptr<const char*> argv =
      std::unique_ptr<const char*>(new const char*[num_options + 1]);
  constexpr char kDummyDelegateParsing[] = "dummy_delegate_parsing";
  argv.get()[0] = kDummyDelegateParsing;

  std::vector<std::string> option_args;
  option_args.reserve(num_options);
  for (int i = 0; i < num_options; ++i) {
    option_args.emplace_back("--");
    option_args.rbegin()->append(options_keys[i]);
    option_args.rbegin()->push_back('=');
    option_args.rbegin()->append(options_values[i]);
    argv.get()[i + 1] = option_args.rbegin()->c_str();
  }

  // Define command-line flags.
  // ...
  std::vector<tflite::Flag> flag_list = {
      tflite::Flag::CreateFlag(...),
      ...,
      tflite::Flag::CreateFlag(...),
  };

  int argc = num_options + 1;
  if (!tflite::Flags::Parse(&argc, argv.get(), flag_list)) {
    return nullptr;
  }

  return TfLiteDummyDelegateCreate(&options);
}

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

// Defines two symbols that need to be exported to use the TFLite external
// delegate. See tensorflow/lite/delegates/external for details.
TFL_CAPI_EXPORT TfLiteDelegate* tflite_plugin_create_delegate(
    char** options_keys, char** options_values, size_t num_options,
    void (*report_error)(const char*)) {
  return tflite::tools::CreateDummyDelegateFromOptions(
      options_keys, options_values, num_options);
}

TFL_CAPI_EXPORT void tflite_plugin_destroy_delegate(TfLiteDelegate* delegate) {
  TfLiteDummyDelegateDelete(delegate);
}

#ifdef __cplusplus
}
#endif  // __cplusplus

現在,建立對應的 BUILD 目標來建構動態程式庫,如下所示:

cc_binary(
    name = "dummy_external_delegate.so",
    srcs = [
        "external_delegate_adaptor.cc",
    ],
    linkshared = 1,
    linkstatic = 1,
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/tools:command_line_flags",
        "//tensorflow/lite/tools:logging",
    ],
)

建立這個外部委派 .so 檔案後,即可建構二進位檔,或使用預先建構的二進位檔與新的委派項目連結,前提是二進位檔必須與 external_delegate_provider 程式庫連結該程式庫 (如這裡所述)。注意:這個外部委派提供者已連結至現有的測試和工具二進位檔。

如要瞭解如何透過這個外部委派方法對虛擬委派項目進行基準測試,請參閱這裡的說明。您可以在前述的測試和評估工具中使用類似的指令。

值得注意的是,外部委派是 Tensorflow Lite Python 繫結中委派的對應 C++ 實作項目,如這裡所示。因此,在這裡建立的動態外部委派轉接器程式庫可直接與 Tensorflow Lite Python API 搭配使用。

資源

OS ARCH BINARY_NAME
Linux x86_64
實驗組
aarch64
Android 實驗組
aarch64