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

Một LiteRT Chế độ Uỷ quyền cho phép bạn chạy các mô hình (một phần hoặc toàn bộ) trên một bộ 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 TPU Edge (Tensor) Xử lý) để suy luận. Điều này mang đến cho nhà phát triển đã tách riêng khỏi TFLite mặc định để tăng tốc độ suy luận.

Biểu đồ dưới đây tóm tắt những người được uỷ quyền. Bạn có thể xem thêm thông tin chi tiết trong các phần dưới đây.

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

Khi nào tôi nên tạo uỷ quyền tuỳ chỉnh?

LiteRT có nhiều đại biểu cho các trình tăng tốc mục tiêu, chẳng hạn như GPU, DSP và EdgeTPU.

Việc tạo uỷ quyề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 chưa được người được uỷ quyền hiện tại.
  • 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 trong trường hợp cụ thể.
  • Bạn đang phát triển các biện pháp tối ưu hoá CPU (chẳng hạn như hợp nhất toán tử) có thể giúp 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, chẳng hạn như sau và một biểu đồ uỷ quyền "MyDelegate" giúp triển khai nhanh hơn các phép toán Conv2D và Trung bình.

Biểu đồ gốc

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

Biểu đồ có uỷ quyền

Đồ thị ở trên thu được khi LiteRT chia tách đồ thị ban đầu sau 2 quy tắc:

  • Các thao tác cụ thể có thể được xử lý bởi thực thể đại diện được đưa vào một phân vùng trong khi vẫn đáp ứng được quy trình tính toán ban đầu phần phụ thuộc giữa các toán tử.
  • Mỗi phân vùng được uỷ quyền chỉ có các nút đầu vào và đầu ra không do người đượ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ó thể cũng được gọi là hạt nhân đại biểu) trong biểu đồ ban đầu đánh giá phân vùng trên lệnh gọi được gọi.

Tuỳ thuộc vào mô hình, biểu đồ cuối cùng có thể kết thúc với một hoặc nhiều nút, có nghĩa là một số hoạt động không được uỷ quyền hỗ trợ. Nhìn chung, bạn không muốn có nhiều phân vùng do trình đại diện xử lý vì mỗi phân vùng khi bạn chuyển từ biểu đồ đại biểu sang biểu đồ chính, sẽ có chi phí cho truyền kết quả từ đồ thị con được uỷ quyền sang đồ thị chính để thu được 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 hiệu suất, đặc biệt khi có một lượng lớn bản sao bộ nhớ.

Triển khai uỷ quyền tuỳ chỉnh của riêng bạn

Phương thức ưu tiên để thêm người được uỷ quyền là 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 riêng 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 uỷ quyền, tức là các thao tác được hỗ trợ và một lớp factory để tạo nhân đóng gói biểu đồ uỷ 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++. 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.

Tài sản này có: (Xem định nghĩa)

  • Init(...): lệnh này sẽ được gọi một lần để thực hiện hoạt động khởi tạo một lần bất kỳ.
  • 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 uỷ quyền. Thông thường, bạn muốn tạo bộ nhớ các lượt phân bổ tại đây, vì lệnh này sẽ được gọi mỗi khi thay đổi kích thước tensor.
  • Gọi(...): sẽ được gọi để suy luận.

Ví dụ:

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

TFLite có một bộ công cụ giúp bạn có thể nhanh chóng kiểm thử so với một 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 dữ liệu đầu vào ngẫu nhiên, sau đó lặp lại chạy mô hình trong số lần chạy được chỉ định. Tính năng này in độ trễ tổng hợp số liệu thống kê ở cuối trang.
  • Công cụ suy luận: Đối với một mô hình nhất định, công cụ tạo dữ liệu Gaussian ngẫu nhiên và truyền dữ liệu đó thông qua 2 trình thông dịch TFLite khác nhau, trong đó một trình chạy CPU đơn luồng nhân hệ điều hành và phần còn lại bằng cách sử dụng thông số kỹ thuật do người dùng xác định. Chỉ số này đo lường giá trị tuyệt đối sự khác biệt giữa tensor đầu ra của mỗi trình phiên dịch, trên một cơ sở cho mỗi phần tử. Công cụ này cũng có thể giúp cải thiện độ chính xác của quá trình gỡ lỗi vấn đề.
  • Ngoài ra còn có các công cụ đánh giá theo 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 loạt bài kiểm thử đơn vị hạt nhân và hoạt động vận hành có thể được sử dụng lại để thử nghiệm đại biểu mới với mức độ phù hợp cao hơn đồng thời đảm bảo Đường dẫn thực thi TFLite không bị hỏng.

Để sử dụng lại các bài kiểm tra và công cụ TFLite cho người được uỷ quyền mới, bạn có thể sử dụng một trong hai tuỳ chọn sau:

Chọn phương pháp phù hợp nhất

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

Do đó, cơ chế uỷ quyền bên ngoài sẽ hoạt động với tệp nhị phân của công cụ LiteRT tạo sẵn. Tuy nhiên, mô hình này ít rõ ràng hơn và có thể phức tạp hơn khi thiết lập theo cách tự động kiểm thử tích hợp. Bạn có thể dùng phương pháp của nhà đăng ký tên miền 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

Chiến lược phát hành đĩa đơn uỷ quyền nhà đăng ký tên miền giữ danh sách các trình cung cấp được uỷ quyền, mỗi trình cung cấp này cung cấp một cách dễ dàng để tạo Các thực thể đại diện TFLite dựa trên cờ hiệu dòng lệnh, do đó, thuận tiện cho việc công cụ. Có thể bổ sung phương thức uỷ quyền mới cho tất cả công cụ LiteRT được đề cập ở trên, trước tiên hãy 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 quy tắc XÂY DỰNG. Một ví dụ đầy đủ về điều này quá trình tích hợp được minh hoạ bên dưới (và bạn có thể tìm thấy mã tại đây).

Giả sử bạn có một đại biểu triển khai các API SimpleDelegate và bên ngoài "C" API để tạo/xoá "dummy" này uỷ quyền như minh hoạ dưới đây:

// 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ư dưới đây:

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 BUILD rất quan trọng vì bạn cần phải đảm bảo rằng thư viện luôn được liên kết và không bị trình tối ưu hoá bỏ qua.

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

Giờ hãy thêm hai quy tắc trình bao bọc này vào tệp BUILD để tạo phiên bản của 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 người được uỷ 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ử nhân hệ điều hành TFLite như mô tả tại đây.

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

Trong giải pháp thay thế này, trước tiên bạn tạo một bộ điều hợp uỷ quyền bên ngoài external_delegate_adaptor.cc như minh hoạ dưới đây. Lưu ý rằng phương pháp này ít được ưu tiên hơn một chút so với Lựa chọn 1 như đề cập.

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ư minh hoạ bên dưới:

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 tệp nhị phân hoặc sử dụng các tệp được tạo 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ời gian external_delegate_provider thư viện hỗ trợ cờ dòng lệnh như được mô tả tại đây. Lưu ý: nhà cung cấp dịch vụ uỷ quyền bên ngoài này đã được liên kết với thử nghiệm và công cụ nhị phân.

Tham khảo nội dung mô tả tại đây để xem hình minh hoạ cách đo điểm chuẩn đại biểu giả thông qua tính năng này uỷ quyền bên ngoài. Bạn có thể sử dụng các lệnh tương tự để kiểm thử và các công cụ đánh giá đã đề cập trước đó.

Điều đáng chú ý là uỷ quyền bên ngoài là C++ tương ứng việc triển khai tính năng uỷ quyền trong liên kết Python LiteRT 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ể là được sử dụng trực tiếp với API LiteRT Python.

Tài nguyên

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