Triển khai Người được uỷ quyền tuỳ chỉnh

Với Trình thực thi của TensorFlow Lite, bạn có thể chạy các mô hình của mình (một phần hoặc toàn bộ) trên một trình thực thi khác. Cơ chế này có thể tận dụng nhiều trình tăng tốc trên thiết bị, chẳng hạn như GPU hoặc Edge TPU (Bộ xử lý cảm biến) để dự đoán. Điều này mang đến cho nhà phát triển một phương thức linh hoạt và được tách riêng từ TFLite mặc định để tăng tốc độ dự đoán.

Sơ đồ dưới đây tóm tắt về những người được uỷ quyền, xem thêm thông tin chi tiết ở các phần bên dưới.

Người được uỷ quyền TFLite

Khi nào tôi nên tạo một Đại biểu tuỳ chỉnh?

TensorFlow Lite có nhiều loại đại biểu cho các trình tăng tốc mục tiêu như GPU, DSP và EdgeTPU.

Việc tạo thực thể đại diện của riêng bạn sẽ 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 học máy mới không được bất kỳ uỷ quyền nào hiện có hỗ trợ.
  • Bạn có một trình tăng tốc phần cứng tuỳ chỉnh giúp cải thiện thời gian chạy cho các trường hợp đã biết.
  • Bạn đang phát triển các tính năng tối ưu hoá CPU (chẳng hạn như hợp nhất toán tử) có thể tăng tốc một số mô hình.

Người được uỷ quyền hoạt động như thế nào?

Hãy xem xét một biểu đồ mô hình đơn giản như sau và biểu đồ uỷ quyền "MyDelegate" có cách triển khai nhanh hơn cho các hoạt động Chuyển đổi 2D và Giá trị trung bình.

Biểu đồ ban đầu

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

Biểu đồ có đối tượng uỷ quyền

Biểu đồ trên thu được khi TensorFlow Lite phân tách biểu đồ ban đầu theo hai quy tắc:

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

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

Tuỳ thuộc vào mô hình, biểu đồ cuối cùng có thể có 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 thực thể đại diện hỗ trợ. Nhìn chung, bạn không muốn có nhiều phân vùng do thực thể đại diện xử lý, vì mỗi lần chuyển từ biểu đồ uỷ quyền sang biểu đồ chính, sẽ có một mức hao tổn để chuyển kết quả từ biểu đồ con được uỷ quyền sang biểu đồ chính dẫn đến kết quả do các bản sao bộ nhớ (ví dụ: GPU sang CPU). Mức hao tổn như vậy có thể bù đắp mức tăng hiệu suất, đặc biệt là khi có một số lượng lớn bản sao bộ nhớ.

Triển khai Thực thể đại diện tuỳ chỉnh của riêng bạn

Phương thức ưu tiên để thêm một thực thể đại diện là sử dụng SimpleDelegate API.

Để tạo một thực thể đại diện 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 đại diện cho các khả năng của thực thể đại diện (bao gồm các toán tử được hỗ trợ) và một lớp nhà máy để tạo một hạt nhân đóng gói biểu đồ được uỷ quyền. Để biết thêm thông tin 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ã sẽ giải thích chi tiết từng API.

2 – SimpleDelegateKernelInterface

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

Có: (Xem định nghĩa)

  • Init(...): sẽ được gọi một lần để thực hiện mọi thao tác khởi tạo một lần.
  • Chuẩn bị(...): được gọi cho mỗi thực thể 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ớ tại đây, vì thao tác này sẽ được gọi mỗi khi tensor được thay đổi kích thước.
  • Gọi(...): hàm này sẽ được gọi để dự đoán.

Ví dụ:

Trong ví dụ này, bạn sẽ tạo một thực thể đại diện rất đơn giản, chỉ có thể hỗ trợ 2 loại thao tác (ADD) và (SUB) chỉ với 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, hãy tạo nhân hệ điều hành uỷ quyền 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_;
};


Đo điểm chuẩn và đánh giá thực thể đại diện mới

TFLite có một bộ công cụ mà bạn có thể nhanh chóng thử nghiệm so với mô hình TFLite.

  • Công cụ đo điểm chuẩn mô hình: Công cụ này sẽ sử dụng một mô hình TFLite, tạo dữ liệu đầu vào ngẫu nhiên, sau đó liên tục chạy mô hình đó trong số lần chạy được chỉ định. Công cụ này in số liệu thống kê tổng hợp về độ trễ ở cuối.
  • Inference Diff Tool: Đối với một mô hình nhất định, công cụ này sẽ tạo dữ liệu Gaussian ngẫu nhiên rồi truyền dữ liệu đó qua hai trình thông dịch TFLite khác nhau, một trình chạy hạt nhân CPU đơn luồng và trình thông dịch còn lại sử dụng thông số kỹ thuật do người dùng xác định. Công cụ này đo lường sự khác biệt tuyệt đối giữa các tensor đầu ra từ mỗi phiên dịch trên cơ sở từng phần tử. Công cụ này cũng có thể giúp 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á cụ thể cho từng nhiệm vụ, để phân loại hình ảnh và phát hiện đối tượng. Bạn có thể tìm thấy các công cụ này tại đây

Ngoài ra, TFLite có một tập hợp lớn các bài kiểm thử đơn vị hạt nhân và đơn vị vận hành có thể dùng lại để kiểm thử thực thể đại biểu mới với mức độ phù hợp cao 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 thử và công cụ TFLite cho thực thể đại diện mới, bạn có thể sử dụng một trong hai tuỳ chọn sau:

Chọn phương pháp tốt nhất

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

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

Cách 1: Tận dụng nhà đăng ký tên miền được uỷ quyền

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

Giả sử bạn có một thực thể đại diện triển khai API SimpleDelegate và các API "C" bên ngoài để tạo/xoá đại biểu "giả" này như sau:

// 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 một 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*) {});
}

Định nghĩa quy tắc XÂY DỰNG 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 do trình tối ưu hoá thả.

#### 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 2 quy tắc trình bao bọc này vào tệp BUILD để tạo một phiên bản của Công cụ đo điểm chuẩn và Công cụ dự đoán, cũng như các công cụ đánh giá khác có thể chạy với ủy quyền 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ể cắm trình cung cấp uỷ quyền này vào các chương trình kiểm thử hạt nhân TFLite như mô tả tại đây.

Cách 2: Tận dụng sự được uỷ quyền bên ngoài

Trong phương án này, trước tiên bạn sẽ tạo một bộ điều hợp uỷ quyền bên ngoài external_delegate_adaptor.cc như minh hoạ bên dưới. Lưu ý rằng phương pháp này ít được ưa thích hơn một chút so với Cách 1 đã đề cập ở 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 để tạo thư viện động như hiển thị 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ạo tệp .so uỷ quyền bên ngoài này, bạn có thể tạo các tệp nhị phân hoặc sử dụng các tệp nhị phân tạo sẵn để chạy với thực thể 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ác cờ hiệu dòng lệnh như mô tả tại đây. Lưu ý: trình cung cấp uỷ quyền bên ngoài này đã được liên kết với các tệp nhị phân kiểm thử và công cụ hiện có.

Hãy tham khảo phần mô tả tại đây để biết hình minh hoạ cách đo điểm chuẩn thực thể đại diện giả thông qua phương pháp uỷ quyền 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ập trước đó.

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

Tài nguyên

Hệ điều hành Hàm ARCH BINARY_NAME
Linux x86_64
nhóm thử nghiệm
aarch64
Android nhóm thử nghiệm
aarch64