Triển khai một đại biểu tùy chỉnh

Đại biểu TensorFlow Lite là gì?

Đại biểu TensorFlow Lite cho phép bạn chạy các mô hình của mình (một phần hoặc toàn bộ) trên một người thực thi khác. Cơ chế này có thể tận dụng nhiều bộ tăng tốc trên thiết bị như GPU hoặc Edge TPU (Bộ xử lý Tensor) để suy luận. Điều này cung cấp cho các nhà phát triển một phương pháp linh hoạt và tách rời khỏi TFLite mặc định để tăng tốc độ suy luận.

Sơ đồ dưới đây tóm tắt các đại biểu, chi tiết hơn ở các phần bên dưới.

TFLite Delegates

Khi nào tôi nên tạo Đại biểu tùy chỉnh?

TensorFlow Lite có nhiều đại biểu khác nhau cho các bộ tăng tốc mục tiêu như GPU, DSP, EdgeTPU và các khung như Android NNAPI.

Tạo đại biểu của riêng bạn rất hữu ích trong các trường hợp sau:

  • Bạn muốn tích hợp một công cụ suy luận ML mới chưa được bất kỳ đại biểu hiện có nào hỗ trợ.
  • Bạn có bộ tăng tốc phần cứng tùy chỉnh giúp cải thiện thời gian chạy cho các tình huống đã biết.
  • Bạn đang phát triển các tính năng tối ưu hóa CPU (chẳng hạn như kết hợp toán tử) có thể tăng tốc một số kiểu máy nhất định.

Các đại biểu làm việc như thế nào?

Hãy xem xét một biểu đồ mô hình đơn giản như sau và một đại biểu “MyDelegate” có khả năng triển khai nhanh hơn cho các hoạt động Conv2D và Mean.

Original graph

Sau khi áp dụng “MyDelegate” này, biểu đồ TensorFlow Lite ban đầu sẽ được cập nhật như sau:

Graph with delegate

Biểu đồ trên thu được khi TensorFlow Lite chia biểu đồ gốc theo hai quy tắc:

  • Các hoạt động cụ thể mà người được ủy quyền có thể xử lý sẽ được đưa vào một phân vùng trong khi vẫn đáp ứng được sự phụ thuộc quy trình tính toán ban đầu giữa các hoạt động.
  • Mỗi phân vùng được ủy quyền chỉ có các nút đầu vào và đầu ra không được đại biểu xử lý.

Mỗi phân vùng do đại biểu xử lý sẽ được thay thế bằng nút đại biểu (cũng có thể được gọi là hạt nhân đại biểu) trong biểu đồ ban đầu để đánh giá phân vùng trong lệnh gọi của nó.

Tùy thuộc vào mô hình, biểu đồ cuối cùng có thể kết thúc bằng một hoặc nhiều nút, điều này có nghĩa là một số hoạt động không được đại biểu hỗ trợ. Nói chung, bạn không muốn có nhiều phân vùng được đại biểu xử lý, vì mỗi lần bạn chuyển từ biểu đồ đại biểu sang biểu đồ chính, sẽ có một chi phí chung để chuyển kết quả từ biểu đồ con được ủy quyền sang biểu đồ chính do bộ nhớ bản sao (ví dụ: GPU sang CPU). Chi phí hoạt động như vậy có thể bù lại mức tăng hiệu suất, đặc biệt khi có số lượng lớn bản sao bộ nhớ.

Triển khai đại biểu tùy chỉnh của riêng bạn

Phương pháp ưa thích để thêm đại biểu là sử dụng API SimpleDelegate .

Để tạo một đại biểu mới, bạn cần triển khai 2 giao diện và cung cấp cách triển khai của riêng bạn cho các phương thức giao diện.

1 - SimpleDelegateInterface

Lớp này thể hiện khả năng của đại biểu, những hoạt động nào được hỗ trợ và lớp xuất xưởng để tạo hạt nhân đóng gói biểu đồ được ủy quyền. Để biết thêm chi tiết, hãy xem giao diện được xác định trong tệp tiêu đề C++ này. Các nhận xét trong mã giải thích chi tiết từng API.

2 - SimpleDelegateKernelInterface

Lớp này gói gọn logic để khởi tạo/chuẩn bị/và chạy phân vùng được ủy quyền.

Nó có: (Xem định nghĩa )

  • Init(...): sẽ được gọi một lần để thực hiện bất kỳ khởi tạo một lần nào.
  • Chuẩn bị (...): được gọi cho từng phiên bản khác nhau của nút này - điều này xảy ra nếu bạn có nhiều phân vùng được ủy quyền. Thông thường, bạn muốn thực hiện phân bổ bộ nhớ ở đây, vì điều này sẽ được gọi mỗi khi các tensor được thay đổi kích thước.
  • Invoke(...): sẽ được gọi để suy luận.

Ví dụ

Trong ví dụ này, bạn sẽ tạo một đại biểu rất đơn giản, chỉ có thể hỗ trợ 2 loại hoạt động (ADD) và (SUB) chỉ với các tensor 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>();
  }
};

Tiếp theo, tạo kernel đại biểu của riêng bạn bằng cách kế thừa từ 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_;
};


Điểm chuẩn và đánh giá đại biểu mới

TFLite có một bộ công cụ mà bạn có thể nhanh chóng kiểm tra dựa trên mô hình TFLite.

  • Công cụ điểm chuẩn mô hình : Công cụ này lấy mô hình TFLite, tạo đầu vào ngẫu nhiên và sau đó chạy mô hình liên tục trong một số lần chạy được chỉ định. Nó in số liệu thống kê độ trễ tổng hợp ở cuối.
  • Công cụ suy luận khác biệt : Đối với một mô hình nhất định, công cụ này tạo ra dữ liệu Gaussian ngẫu nhiên và chuyển nó qua hai trình thông dịch TFLite khác nhau, một chạy nhân CPU luồng đơn và một chạy sử dụng thông số kỹ thuật do người dùng xác định. Nó đo lường sự khác biệt tuyệt đối giữa các tensor đầu ra từ mỗi trình thông dịch, trên cơ sở từng phần tử. Công cụ này cũng có thể hữu ích để gỡ lỗi các vấn đề về độ chính xác.
  • Ngoài ra còn có các công cụ đánh giá nhiệm vụ cụ thể để phân loại hình ảnh và phát hiện đối tượng. Những công cụ này có thể được tìm thấy ở đây

Ngoài ra, TFLite còn có một bộ lớn các bài kiểm tra hạt nhân và đơn vị op có thể được sử dụng lại để kiểm tra đại biểu mới với phạm vi bao phủ rộng hơn và để đảm bảo đường dẫn thực thi TFLite thông thường không bị hỏng.

Để sử dụng lại các bài kiểm tra và công cụ TFLite cho đại biểu mới, bạn có thể sử dụng một trong hai tùy chọn sau:

Lựa chọn cách tiếp cận tốt nhất

Cả hai phương pháp đều yêu cầu một số thay đổi như chi tiết bên dưới. Tuy nhiên, cách tiếp cận đầu tiên liên kết đại biểu một cách tĩnh và yêu cầu xây dựng lại các công cụ kiểm tra, đo điểm chuẩn và đánh giá. Ngược lại, cách thứ hai biến đại biểu thành thư viện dùng chung và yêu cầu bạn hiển thị các phương thức tạo/xóa khỏi thư viện dùng chung.

Do đó, cơ chế đại biểu bên ngoài sẽ hoạt động với các tệp nhị phân công cụ Tensorflow Lite dựng sẵn của TFLite. Tuy nhiên, nó ít rõ ràng hơn và có thể phức tạp hơn khi thiết lập trong các thử nghiệm tích hợp tự động. Sử dụng phương pháp đăng ký đại biểu để rõ ràng hơn.

Tùy chọn 1: Tận dụng công ty đăng ký đại biểu

Nhà đăng ký đại biểu giữ một danh sách các nhà cung cấp đại biểu, mỗi nhà cung cấp đều cung cấp một cách dễ dàng để tạo các đại biểu TFLite dựa trên cờ dòng lệnh và do đó thuận tiện cho việc sử dụng công cụ. Để thêm đại biểu mới vào tất cả các công cụ Tensorflow Lite được đề cập ở trên, trước tiên bạn phải tạo một nhà cung cấp đại biểu mới như thế này , sau đó chỉ thực hiện một số thay đổi đối với các quy tắc BUILD. Một ví dụ đầy đủ về quá trình tích hợp này được hiển thị bên dưới (và có thể tìm thấy mã ở đây ).

Giả sử bạn có một đại biểu triển khai API SimpleDelegate và các API "C" bên ngoài để tạo/xóa đại biểu 'giả' này như hiển thị bên dưới:

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

Để tích hợp “DummyDelegate” với Công cụ đo điểm chuẩn và Công cụ suy luận, hãy xác định DelegateProvider như bên dưới:

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*) {});
}

Các định nghĩa quy tắc BUILD rất quan trọng vì bạn cần đảm bảo rằng thư viện luôn được liên kết và không bị trình tối ưu hóa loại bỏ.

#### 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.
)

Bây giờ, hãy thêm hai quy tắc trình bao bọc này vào tệp BUILD của bạn để tạo phiên bản Công cụ điểm chuẩn và Công cụ suy luận cũng như các công cụ đánh giá khác có thể chạy với đại biểu của riêng bạn.

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",
    ],
)

Bạn cũng có thể kết nối nhà cung cấp đại biểu này với các bài kiểm tra hạt nhân TFLite như được mô tả ở đây .

Tùy chọn 2: Tận dụng đại biểu bên ngoài

Trong giải pháp thay thế này, trước tiên bạn tạo bộ điều hợp đại biểu bên ngoài external_delegate_adaptor.cc như hiển thị bên dưới. Lưu ý, cách tiếp cận này ít được ưu tiên hơn so với Tùy chọn 1 như đã nói ở trên .

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

Bây giờ hãy tạo mục tiêu BUILD tương ứng để xây dựng thư viện động như dưới đây:

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",
    ],
)

Sau khi tệp .so đại biểu bên ngoài này được tạo, bạn có thể xây dựng các tệp nhị phân hoặc sử dụng các tệp nhị phân dựng sẵn để chạy với đại biểu mới miễn là tệp nhị phân được liên kết với thư viện external_delegate_provider hỗ trợ cờ dòng lệnh như được mô tả ở đây . Lưu ý: nhà cung cấp đại biểu bên ngoài này đã được liên kết với các tệp nhị phân thử nghiệm và công cụ hiện có.

Hãy tham khảo phần mô tả ở đây để biết minh họa về cách đánh giá đại biểu giả thông qua phương pháp đại biểu bên ngoài này. Bạn có thể sử dụng các lệnh tương tự cho các công cụ kiểm tra và đánh giá được đề cập trước đó.

Điều đáng chú ý là đại biểu bên ngoài là cách triển khai C++ tương ứng của đại biểu trong liên kết Tensorflow Lite Python như được hiển thị ở đây . Do đó, thư viện bộ điều hợp đại biểu bên ngoài động được tạo ở đây có thể được sử dụng trực tiếp với API Python Tensorflow Lite.

Tài nguyên

hệ điều hành Vòm BINARY_NAME
Linux x86_64
cánh tay
aarch64
Android cánh tay
aarch64