Bantuan melindungi Great Barrier Reef dengan TensorFlow pada Kaggle Bergabung Tantangan

Menerapkan Delegasi Kustom

Apa itu Delegasi TensorFlow Lite?

Delegate TensorFlow Lite memungkinkan Anda menjalankan model (sebagian atau keseluruhan) pada eksekutor lain. Mekanisme ini dapat memanfaatkan berbagai akselerator pada perangkat seperti GPU atau Edge TPU (Tensor Processing Unit) untuk inferensi. Ini memberi pengembang metode yang fleksibel dan terpisah dari TFLite default untuk mempercepat inferensi.

Diagram di bawah merangkum para delegasi, lebih detailnya di bagian di bawah ini.

TFLite Delegates

Kapan saya harus membuat Delegasi Khusus?

TensorFlow Lite memiliki berbagai macam delegasi untuk akselerator target seperti GPU, DSP, EdgeTPU, dan framework seperti Android NNAPI.

Membuat delegasi Anda sendiri berguna dalam skenario berikut:

  • Anda ingin mengintegrasikan mesin inferensi ML baru yang tidak didukung oleh delegasi yang sudah ada.
  • Anda memiliki akselerator perangkat keras khusus yang meningkatkan waktu proses untuk skenario yang diketahui.
  • Anda sedang mengembangkan pengoptimalan CPU (seperti penggabungan operator) yang dapat mempercepat model tertentu.

Bagaimana cara kerja delegasi?

Pertimbangkan grafik model sederhana seperti berikut, dan delegasi "MyDelegate" yang memiliki implementasi lebih cepat untuk operasi Conv2D dan Mean.

Original graph

Setelah menerapkan "MyDelegate" ini, grafik TensorFlow Lite asli akan diperbarui seperti berikut:

Graph with delegate

Grafik di atas diperoleh saat TensorFlow Lite membagi grafik asli mengikuti dua aturan:

  • Operasi khusus yang dapat ditangani oleh delegasi dimasukkan ke dalam partisi sambil tetap memenuhi dependensi alur kerja komputasi asli di antara operasi.
  • Setiap partisi yang akan didelegasikan hanya memiliki node input dan output yang tidak ditangani oleh delegasi.

Setiap partisi yang ditangani oleh delegasi diganti dengan node delegasi (juga dapat disebut sebagai kernel delegasi) dalam grafik asli yang mengevaluasi partisi pada panggilan pemanggilannya.

Bergantung pada modelnya, grafik akhir dapat berakhir dengan satu atau lebih node, yang terakhir berarti bahwa beberapa operasi tidak didukung oleh delegasi. Secara umum, Anda tidak ingin memiliki banyak partisi yang ditangani oleh delegasi, karena setiap kali Anda beralih dari delegasi ke grafik utama, ada biaya tambahan untuk meneruskan hasil dari subgraf yang didelegasikan ke grafik utama yang dihasilkan karena memori salinan (misalnya, GPU ke CPU). Overhead seperti itu dapat mengimbangi peningkatan kinerja terutama bila ada salinan memori dalam jumlah besar.

Menerapkan delegasi Kustom Anda sendiri

Metode yang disukai untuk menambahkan delegasi menggunakan SimpleDelegate API .

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

1 - SimpleDelegateInterface

Kelas ini mewakili kemampuan delegasi, operasi mana yang didukung, dan kelas pabrik untuk membuat kernel yang merangkum grafik yang didelegasikan. Untuk lebih jelasnya, lihat antarmuka yang ditentukan dalam file header C ++ ini . Komentar dalam kode menjelaskan setiap API secara mendetail.

2 - SimpleDelegateKernelInterface

Kelas ini merangkum logika untuk menginisialisasi / mempersiapkan / dan menjalankan partisi yang didelegasikan.

Ini memiliki: (Lihat definisi )

  • Init (...): yang akan dipanggil sekali untuk melakukan inisialisasi satu kali.
  • Persiapkan (...): dipanggil untuk setiap instance berbeda dari node ini - ini terjadi jika Anda memiliki beberapa partisi yang didelegasikan. Biasanya Anda ingin melakukan alokasi memori di sini, karena ini akan dipanggil setiap kali tensor diubah ukurannya.
  • Invoke (...): 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 kernel 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_;
};


.dll

Benchmark dan evaluasi delegasi baru

TFLite memiliki seperangkat alat yang dapat Anda uji dengan cepat terhadap model TFLite.

  • Alat Tolok Ukur Model : Alat ini mengambil model TFLite, menghasilkan input acak, lalu berulang kali menjalankan model tersebut untuk sejumlah operasi yang ditentukan. Ini mencetak statistik latensi agregat di bagian akhir.
  • Alat Diff Inferensi : Untuk model tertentu, alat tersebut menghasilkan data Gaussian acak dan meneruskannya melalui dua interpreter TFLite yang berbeda, satu menjalankan kernel CPU berulir tunggal dan yang lainnya menggunakan spesifikasi yang ditentukan pengguna. Ini mengukur perbedaan absolut antara tensor keluaran dari setiap interpreter, pada basis per elemen. Alat ini juga dapat membantu untuk men-debug masalah akurasi.
  • Ada juga alat evaluasi khusus tugas, untuk klasifikasi gambar dan deteksi objek. Alat-alat ini dapat ditemukan di sini

Selain itu, TFLite memiliki serangkaian pengujian kernel dan unit operasi yang besar yang dapat digunakan kembali untuk menguji delegasi baru dengan cakupan yang lebih luas dan untuk memastikan jalur eksekusi TFLite reguler tidak rusak.

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

Memilih pendekatan terbaik

Kedua pendekatan tersebut membutuhkan beberapa perubahan seperti yang dijelaskan di bawah ini. Namun, pendekatan pertama menghubungkan delegasi secara statis dan memerlukan pembuatan ulang alat pengujian, pembandingan, dan evaluasi. Sebaliknya, yang kedua menjadikan delegasi sebagai pustaka bersama dan mengharuskan Anda untuk mengekspos metode buat / hapus dari pustaka bersama.

Hasilnya, mekanisme delegasi eksternal akan bekerja dengan biner alat Tensorflow Lite TFLite yang sudah dibuat sebelumnya . Namun ini kurang eksplisit dan mungkin lebih rumit untuk disiapkan dalam pengujian integrasi otomatis. Gunakan pendekatan registrar delegasi untuk kejelasan yang lebih baik.

Opsi 1: Manfaatkan registrar delegasi

Registrar delegasi menyimpan daftar penyedia delegasi, yang masing-masing menyediakan cara mudah untuk membuat delegasi TFLite berdasarkan tanda baris perintah, dan karenanya, nyaman untuk perkakas. Untuk pasang di delegasi baru untuk semua alat Tensorflow Lite yang disebutkan di atas, Anda pertama kali membuat penyedia delegasi baru seperti ini satu , dan kemudian membuat hanya beberapa perubahan aturan BUILD. Contoh lengkap dari proses integrasi ini ditunjukkan di bawah (dan kode dapat ditemukan di sini ).

Dengan asumsi Anda memiliki delegasi yang mengimplementasikan API SimpleDelegate, dan API "C" eksternal untuk membuat / menghapus delegasi 'dummy' ini 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 penting karena Anda perlu memastikan bahwa pustaka selalu ditautkan dan tidak dijatuhkan 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 dua aturan pembungkus ini dalam file BUILD Anda untuk membuat versi Alat Tolok Ukur dan Alat Inferensi, dan 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 menghubungkan penyedia delegasi ini ke pengujian kernel TFLite seperti yang dijelaskan di sini .

Opsi 2: Manfaatkan delegasi eksternal

Dalam alternatif ini, Anda terlebih dahulu membuat adaptor delegasi eksternal external_delegate_adaptor.cc seperti yang ditunjukkan di bawah ini. Perhatikan, pendekatan ini sedikit kurang disukai dibandingkan dengan Opsi 1 seperti yang telah disebutkan sebelumnya .

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 perpustakaan 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 ini dibuat, Anda dapat membuat biner atau menggunakan yang telah dibuat sebelumnya untuk dijalankan dengan delegasi baru selama biner tersebut ditautkan dengan library external_delegate_provider yang mendukung tanda baris perintah seperti yang dijelaskan di sini . Catatan: penyedia delegasi eksternal ini telah ditautkan ke pengujian yang ada dan biner perkakas.

Lihat deskripsi di sini untuk ilustrasi tentang cara membandingkan delegasi tiruan melalui pendekatan delegasi eksternal ini. Anda dapat menggunakan perintah serupa untuk alat pengujian dan evaluasi yang disebutkan sebelumnya.

Perlu diperhatikan bahwa delegasi eksternal adalah implementasi C ++ yang sesuai dari delegasi tersebut dalam binding Python Tensorflow Lite seperti yang ditampilkan di sini . Oleh karena itu, pustaka adaptor delegasi eksternal dinamis yang dibuat di sini dapat langsung digunakan dengan Tensorflow Lite Python API.

Sumber daya

OS LENGKUNGAN BINARY_NAME
Linux x86_64
lengan
aarch64
Android lengan
aarch64