Mengimplementasikan Delegasi Khusus

LiteRT Opsi Delegasi memungkinkan Anda untuk menjalankan model Anda (sebagian atau keseluruhan) pada eksekutor lain. Mekanisme ini dapat memanfaatkan berbagai akselerator di perangkat seperti GPU atau Edge TPU (Tensor Unit Pemrosesan) untuk inferensi. Hal ini memberi developer fleksibilitas dan terpisah dari TFLite {i>default<i} untuk mempercepat inferensi.

Diagram di bawah ini merangkum para penerima delegasi, detail selengkapnya di bagian bawah.

Delegasi TFLite

Kapan saya harus membuat delegasi Kustom?

LiteRT memiliki beragam delegasi untuk akselerator target seperti GPU, DSP, dan EdgeTPU.

Membuat delegasi Anda sendiri berguna dalam skenario berikut:

  • Anda ingin mengintegrasikan mesin inferensi ML baru yang tidak didukung oleh delegasi yang ada.
  • Anda memiliki akselerator hardware khusus yang meningkatkan runtime untuk yang signifikan.
  • Anda mengembangkan pengoptimalan CPU (seperti penggabungan operator) yang dapat mempercepat model tertentu.

Bagaimana cara kerja penerima delegasi?

Pertimbangkan grafik model sederhana seperti berikut, dan delegasi "MyDelegate" yang memiliki penerapan lebih cepat untuk operasi Konv2D dan Rata-rata.

Grafik asli

Setelah menerapkan "MyDelegate", grafik LiteRT asli akan menjadi diperbarui seperti berikut:

Grafik dengan delegasi

Grafik di atas diperoleh saat LiteRT membagi grafik asli dua aturan berikut:

  • Operasi tertentu yang dapat ditangani oleh delegasi dimasukkan ke dalam sebuah partisi sambil tetap memenuhi alur kerja komputasi awal dependensi di antara operasi.
  • Setiap partisi yang akan didelegasikan hanya memiliki {i>node<i} input dan {i>output<i} yang tidak ditangani oleh delegasi.

Setiap partisi yang ditangani oleh delegasi diganti oleh {i>node<i} delegasi (dapat juga disebut sebagai kernel delegasi) dalam grafik asli yang mengevaluasi partisi pada panggilan pemanggilannya.

Bergantung pada modelnya, grafik akhir bisa saja memiliki satu atau beberapa node, yang kedua berarti bahwa beberapa operasi tidak didukung oleh delegasi. Secara umum, Anda dapat tidak ingin memiliki beberapa partisi yang ditangani oleh delegasi, karena masing-masing setiap kali Anda beralih dari delegasi ke grafik utama, terdapat {i>overhead<i} untuk meneruskan hasil dari subgrafik yang didelegasikan ke grafik utama yang menghasilkan karena salinan memori (misalnya, GPU ke CPU). {i>Overhead<i} tersebut mungkin diimbangi peningkatan kinerja terutama ketika ada salinan memori dalam jumlah besar.

Mengimplementasikan delegasi Kustom Anda sendiri

Metode yang disukai untuk menambah delegasi adalah menggunakan SimpleDelegate API.

Untuk membuat delegasi baru, Anda perlu mengimplementasikan 2 antarmuka dan menyediakan implementasinya sendiri untuk metode antarmuka.

1—SimpleDelegateInterface

Kelas ini merepresentasikan kemampuan delegasi, yang operasinya didukung, dan kelas factory untuk membuat {i>kernel<i} yang mengenkapsulasi grafik delegasi. Untuk detail selengkapnya, lihat antarmuka yang didefinisikan dalam File header C++. Komentar dalam kode menjelaskan setiap API secara mendetail.

2—SimpleDelegateKernelInterface

Class ini merangkum logika untuk melakukan inisialisasi / menyiapkan / dan menjalankan partisi yang didelegasikan.

Konten ini memiliki: (Lihat definisi )

  • Init(...): yang akan dipanggil sekali untuk melakukan inisialisasi satu kali.
  • Prepare(...): dipanggil untuk setiap instance yang berbeda dari node ini - ini terjadi jika Anda memiliki beberapa partisi yang didelegasikan. Biasanya Anda ingin mengingat alokasi di sini, karena ini akan dipanggil setiap kali tensor diubah ukurannya.
  • Panggil(...): yang akan dipanggil untuk inferensi.

Contoh

Dalam contoh ini, Anda akan membuat delegasi yang sangat sederhana yang hanya dapat mendukung 2 jenis operasi (ADD) dan (SUB) dengan tensor float32 saja.

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

Selanjutnya, buat {i>kernel<i} delegasi Anda sendiri dengan mewarisi dari 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_;
};

Membuat tolok ukur dan mengevaluasi delegasi baru

TFLite memiliki seperangkat alat yang dapat diuji dengan cepat terhadap model TFLite.

  • Alat Tolok Ukur Model: Alat ini mengambil model TFLite, menghasilkan input acak, lalu berulang kali menjalankan model untuk sejumlah operasi tertentu. Solusi ini mencetak latensi gabungan statistik di akhir.
  • Alat Perbedaan Inferensi: Untuk model tertentu, alat ini menghasilkan data Gaussian acak dan meneruskannya melalui dua penerjemah TFLite yang berbeda, satu menjalankan CPU thread tunggal dan yang lainnya menggunakan spesifikasi yang ditentukan pengguna. Ini mengukur nilai absolut perbedaan antara tensor output dari setiap penafsir, pada per elemen. Alat ini juga dapat berguna untuk akurasi proses debug masalah performa.
  • Ada juga alat evaluasi khusus tugas, untuk klasifikasi gambar dan deteksi objek. Alat-alat ini dapat ditemukan di sini

Selain itu, TFLite memiliki serangkaian besar pengujian unit operasi dan {i>kernel<i} yang dapat digunakan kembali untuk menguji delegasi baru dengan cakupan yang lebih besar dan untuk Jalur eksekusi TFLite tidak rusak.

Agar dapat menggunakan kembali pengujian dan alat TFLite untuk delegasi baru, Anda dapat menggunakan salah satu dari dua opsi berikut:

Memilih pendekatan terbaik

Kedua pendekatan tersebut memerlukan beberapa perubahan seperti yang dijelaskan di bawah ini. Namun, hal pertama menautkan delegasi secara statis dan membutuhkan pembangunan ulang pengujian, {i>benchmark <i}dan evaluasi. Sebaliknya, yang kedua membuat delegasikan sebagai pustaka bersama dan mengharuskan Anda mengekspos fungsi buat/hapus metode dari pustaka bersama.

Akibatnya, mekanisme delegasi eksternal akan bekerja dengan perangkat lunak biner alat LiteRT bawaan. Namun, kurang eksplisit dan mungkin lebih rumit untuk disiapkan secara otomatis pengujian integrasi. Gunakan pendekatan registrar delegasi agar lebih jelas.

Opsi 1: Manfaatkan registrar delegasi

Tujuan registrar delegasi menyimpan daftar penyedia delegasi, yang masing-masing menyediakan cara mudah untuk membuat TFLite delegasi berdasarkan penanda baris perintah, dan karenanya, mudah digunakan dengan alat yang sama. Untuk menyertakan delegasi baru ke semua alat LiteRT yang disebutkan di atas, pertama-tama Anda membuat penyedia delegasi baru, dan kemudian hanya membuat sedikit perubahan pada aturan BUILD. Contoh lengkapnya proses integrasi ditunjukkan di bawah ini (dan kode dapat ditemukan di sini).

Dengan asumsi Anda memiliki delegasi yang mengimplementasikan API SimpleDelegate, dan eksternal "C" API untuk membuat/menghapus 'dummy' ini delegasikan seperti yang ditunjukkan di bawah ini:

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

Untuk mengintegrasikan “DummyDelegate” dengan Alat Benchmark dan Alat Inferensi, tentukan DelegateProvider seperti di bawah ini:

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

Definisi aturan BUILD sangatlah penting karena Anda perlu memastikan bahwa library selalu ditautkan dan tidak dihapus oleh pengoptimal.

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

Sekarang tambahkan kedua aturan wrapper ini dalam file BUILD Anda untuk membuat versi Alat Tolok Ukur dan Alat Inferensi, serta alat evaluasi lainnya, yang dapat berjalan dengan delegasi Anda sendiri.

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

Anda juga dapat menyambungkan penyedia delegasi ini ke pengujian kernel TFLite seperti yang dijelaskan di sini.

Opsi 2: Manfaatkan delegasi eksternal

Dalam alternatif ini, pertama-tama Anda membuat adaptor delegasi eksternal, external_delegate_adaptor.cc sebagaimana ditunjukkan di bawah ini. Perhatikan bahwa pendekatan ini sedikit kurang disukai dibandingkan dengan Opsi 1 seperti yang telah disebutkan di atas.

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

Sekarang, buat target BUILD yang sesuai untuk membangun library dinamis seperti yang ditunjukkan di bawah ini:

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

Setelah file .so delegasi eksternal dibuat, Anda dapat membangun biner atau menggunakan yang sudah dibangun sebelumnya untuk dijalankan dengan delegasi baru selama biner ditautkan dengan tindakan external_delegate_provider yang mendukung flag command line seperti yang dijelaskan di sini. Catatan: penyedia delegasi eksternal ini telah ditautkan ke platform biner pengujian dan alat.

Lihat deskripsi di sini untuk ilustrasi cara mengukur delegasi contoh melalui delegasi eksternal. Anda dapat menggunakan perintah serupa untuk pengujian dan alat evaluasi yang disebutkan sebelumnya.

Perlu diperhatikan bahwa delegasi eksternal adalah C++ yang sesuai implementasi delegasi dalam binding Python LiteRT seperti yang ditunjukkan di sini. Oleh karena itu, library adaptor delegasi eksternal dinamis yang dibuat di sini dapat langsung digunakan dengan LiteRT Python API.

Resource

OS ARCH BINARY_NAME
Linux x86_64
grup
aarch64
Android grup
aarch64