Operator kustom

Karena library operator bawaan LiteRT hanya mendukung jumlah operator TensorFlow, tidak semua model dapat dikonversi. Untuk mengetahui detailnya, lihat kompatibilitas operator.

Untuk memungkinkan konversi, pengguna dapat memberikan implementasi kustom mereka sendiri dari operator TensorFlow yang tidak didukung di LiteRT, yang dikenal sebagai operator kustom. Jika sebaliknya, Anda ingin menggabungkan serangkaian data yang tidak didukung (atau didukung) Operator TensorFlow ke dalam satu operator kustom yang dioptimalkan dan digabungkan, lihat penggabungan operator.

Menggunakan operator kustom terdiri dari empat langkah.

Mari kita lihat contoh menyeluruh operator tf.atan (dinamai sebagai Atan, lihat bagian Membuat Model TensorFlow.) yang didukung di TensorFlow, tetapi tidak didukung di LiteRT.

Operator TensorFlow Text adalah contoh operator kustom. Lihat Tutorial Mengonversi Teks TF ke LiteRT untuk mengetahui contoh kode.

Contoh: Operator Atan kustom

Mari kita lihat contoh dukungan operator TensorFlow yang LiteRT tidak memilikinya. Asumsikan kita menggunakan operator Atan dan bahwa kita membuat model yang sangat sederhana untuk fungsi y = atan(x + offset), di mana offset dapat dilatih.

Membuat Model TensorFlow

Cuplikan kode berikut melatih model TensorFlow sederhana. Model ini hanya berisi operator kustom bernama Atan, yang merupakan fungsi y = atan(x + offset), dengan offset dapat dilatih.

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

Pada tahap ini, jika Anda mencoba membuat model LiteRT dengan penanda pengonversi, Anda akan mendapatkan pesan error berikut:

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

Mengonversi ke Model LiteRT

Membuat model LiteRT dengan operator kustom, dengan menyetel konverter atribut allow_custom_ops seperti yang ditunjukkan di bawah ini:

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

Pada tahap ini, jika Anda menjalankannya dengan penafsir {i>default<i} menggunakan perintah seperti berikut ini:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

Anda masih akan mendapatkan error:

Encountered unresolved custom op: Atan.

Buat dan daftarkan operator.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

Operator khusus LiteRT didefinisikan menggunakan API pure-C sederhana yang terdiri dari jenis buram (TfLiteRegistrationExternal) dan fungsi terkait.

TfLiteRegistrationExternal adalah jenis buram:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal menyimpan identitas dan implementasi operator. (Perhatikan bahwa operator berbeda dari operand-nya, yang disimpan dalam Node grafik LiteRT untuk node yang memanggil operator.)

Instance jenis ini dibuat dengan panggilan ke TfLiteRegistrationExternalCreate dan dapat dihancurkan dengan memanggil TfLiteRegistrationExternalDelete.

Identitas operator ditetapkan melalui parameter ke fungsi konstruktor TfLiteRegistrationExternalCreate:

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    TfLiteBuiltinOperator builtin_code,  // Normally `TfLiteBuiltinCustom`.
    const char* custom_name,  // The name of the custom op.
    int version  // Normally `1` for the first version of a custom op.
);

Implementasi operator dapat menentukan "metode" dengan tanda tangan berikut. Semua metode ini bersifat opsional, tetapi agar operator berhasil dievaluasi, implementasi operator perlu menentukan dan menetapkan (menggunakan penyetel fungsi) setidaknya metode Prepare dan Invoke.

// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);

// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);

// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
                              TfLiteOpaqueNode* node);

Nama fungsi (atau awalan namespace, untuk C++) dalam implementasi operasi Anda tidak harus cocok dengan nama fungsi dalam cuplikan kode di atas, karena TF API operasi khusus Lite hanya akan menggunakan alamatnya. Sebenarnya kami menyarankan agar Anda mendeklarasikannya dalam namespace anonim atau sebagai fungsi statis.

Tetapi ide yang baik untuk memasukkan nama operator Anda sebagai namespace atau awalan pada nama-nama fungsi ini:

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

C

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

Karena ini adalah C API, "metode" ini diimplementasikan sebagai pointer fungsi C di jenis TfLiteRegistrationExternal, yang ditetapkan dengan meneruskan alamat fungsi implementasi Anda ke fungsi penyetel yang sesuai TfLiteRegistrationExternalSetMethodName:

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

Rujuk ke common.h untuk mengetahui detail tentang TfLiteContext dan TfLiteNode. TfLiteContext memberikan error fasilitas pelaporan dan akses ke objek global, termasuk semua tensor. TfLiteNode memungkinkan implementasi operator mengakses input dan outputnya.

Saat penafsir memuat model, penafsir memanggil metode Init() satu kali untuk setiap model node di grafik. Init() yang ditentukan akan dipanggil lebih dari sekali jika op digunakan beberapa kali dalam grafik. Untuk operasi khusus, buffer konfigurasi akan disediakan, yang berisi flexbuffer yang memetakan nama parameter ke nilainya. Tujuan buffer kosong untuk operasi bawaan karena penafsir telah menguraikan parameter operasi. Implementasi kernel yang memerlukan status harus menginisialisasinya di sini dan mentransfer kepemilikan ke pemanggil. Untuk setiap panggilan Init(), akan ada panggilan yang sesuai ke Free(), yang memungkinkan implementasi untuk membuang buffer yang mungkin telah dialokasikan di Init().

Kapan pun tensor input diubah ukurannya, penafsir akan melalui grafik yang memberitahukan implementasi perubahan. Ini memberi mereka kesempatan untuk mengubah ukuran buffer internal, memeriksa validitas bentuk dan jenis input, serta menghitung ulang bentuk {i>output<i}. Ini semua dilakukan melalui metode Prepare(), dan implementasi dapat mengakses statusnya menggunakan TfLiteOpaqueNodeGetUserData(node).

Terakhir, setiap kali inferensi berjalan, penerjemah melintasi grafik yang memanggil metode Invoke(), dan di sini juga statusnya tersedia sebagai TfLiteOpaqueNodeGetUserData(node).

Operasi kustom dapat diterapkan dengan menentukan "metode" tersebut fungsi, dan kemudian menentukan fungsi yang menampilkan instance TfLiteRegistrationExternal dibuat dengan memanggil TfLiteRegistrationExternalCreate, lalu model metode penyetel:

C++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

C

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

Perhatikan bahwa pendaftaran tidak otomatis dan merupakan panggilan eksplisit ke Fungsi MyCustomOpRegistration harus dibuat (lihat detail di bawah). Sementara BuiltinOpResolver standar (tersedia dari target :builtin_ops) memerlukan pendaftaran bawaan, operasi khusus harus dikumpulkan di library kustom terpisah.

Menentukan kernel dalam runtime LiteRT

Yang perlu kita lakukan untuk menggunakan op di LiteRT adalah menentukan dua fungsi (Prepare dan Eval), dan yang ketiga untuk membuat TfLiteRegistrationExternal:

C++

namespace atan_op {
  namespace {
    TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

      TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
      for (int i=0; i < num_dims; ++i) {
        output_size->data[i] = input->dims->data[i];
      }

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

    TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      float* input_data = static_cast<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(TfLiteOpaqueTensorData(output));

      size_t count = 1;
      int num_dims = TfLiteOpaqueTensorNumDimensions(input);
      for (int i = 0; i < num_dims; ++i) {
        count *= input->dims->data[i];
      }

      for (size_t i = 0; i < count; ++i) {
        output_data[i] = atan(input_data[i]);
      }
      return kTfLiteOk;
    }
  }  // anonymous namespace

  const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

C

static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  float* input_data = static_cast<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(TfLiteOpaqueTensorData(output));

  size_t count = 1;
  int num_dims = TfLiteOpaqueTensorNumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i = 0; i < count; ++i) {
    output_data[i] = atan(input_data[i]);
  }
  return kTfLiteOk;
}

static const TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

Saat menginisialisasi OpResolver, tambahkan op kustom ke dalam resolver (lihat di bawah ini sebagai contoh). Ini akan mendaftarkan operator dengan LiteRT sehingga bahwa LiteRT dapat menggunakan implementasi baru tersebut. Perhatikan bahwa dua langkah terakhir argumen di TfLiteRegistration sesuai dengan AtanPrepare dan AtanEval fungsi yang ditentukan untuk operasi kustom. Jika Anda menggunakan AtanInit dan AtanFree untuk menginisialisasi variabel yang digunakan dalam operasi dan mengosongkan ruang, masing-masing, mereka akan ditambahkan ke dua argumen pertama dari TfLiteRegistration; argumen tersebut ditetapkan ke nullptr dalam contoh ini.

Mendaftarkan operator dengan library kernel

Sekarang kita perlu mendaftarkan operator dengan library {i>kernel<i}. Hal ini dilakukan dengan OpResolver. Di balik layar, penafsir akan memuat library {i>kernel<i} yang akan ditugaskan untuk mengeksekusi masing-masing operator dalam model. Meskipun pustaka {i>default<i} hanya berisi {i>kernel<i}, Anda dapat mengganti/menambahkannya dengan operator {i>library op<i} khusus.

Class OpResolver, yang menerjemahkan kode dan nama operator menjadi nilai , didefinisikan seperti berikut ini:

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

Perhatikan bahwa untuk kompatibilitas mundur, class ini menggunakan jenis konkret yang lebih lama TfLiteRegistration, bukan jenis buram TfLiteRegistrationExternal, tetapi struct TfLiteRegistration berisi kolom registration_external ketik TfLiteRegistrationExternal*.

Class MutableOpResolver dan BuiltinOpResolver berasal dari OpResolver:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

Penggunaan reguler (tanpa operasi kustom) mengharuskan Anda menggunakan BuiltinOpResolver dan tulis:

tflite::ops::builtin::BuiltinOpResolver resolver;

Untuk menambahkan operasi kustom yang dibuat di atas, Anda dapat menggunakan MutableOpResolver, dan panggil AddCustom (sebelum Anda meneruskan resolver ke InterpreterBuilder):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

Jika kumpulan operasi bawaan dianggap terlalu besar, OpResolver baru dapat kode yang dihasilkan berdasarkan subset operasi tertentu, mungkin hanya yang berisi dalam model tertentu. Ini sama dengan pendaftaran selektif TensorFlow (dan versi sederhananya tersedia di direktori tools).

Jika ingin mendefinisikan operator khusus di Java, Anda saat ini harus membangun lapisan JNI kustom sendiri dan mengompilasi AAR Anda sendiri dalam kode jni ini. Demikian pula, jika Anda ingin menentukan operator yang tersedia di Python, Anda bisa menempatkan pendaftaran Anda di Kode wrapper Python.

Perhatikan bahwa proses serupa seperti di atas dapat diikuti untuk mendukung operasi alih-alih menggunakan operator tunggal. Cukup tambahkan operator AddCustom sebanyak mungkin sesuai kebutuhan. Selain itu, MutableOpResolver juga memungkinkan Anda mengganti implementasi bawaan menggunakan AddBuiltin.

Menguji dan membuat profil operator Anda

Untuk membuat profil operasi dengan alat tolok ukur LiteRT, Anda dapat menggunakan alat model tolok ukur untuk LiteRT. Untuk tujuan pengujian, Anda dapat membuat build lokal Anda LiteRT mengetahui operasi kustom Anda dengan menambahkan AddCustom yang sesuai panggil (seperti yang ditampilkan di atas) untuk register.cc

Praktik terbaik

  1. Optimalkan alokasi dan de-alokasi memori dengan hati-hati. Mengalokasikan memori di Prepare lebih efisien daripada di Invoke, dan mengalokasikan memori sebelum sebuah {i>loop <i}lebih baik daripada di setiap iterasi. Menggunakan data tensor sementara daripada malocing diri Anda sendiri (lihat item 2). Gunakan pointer/referensi sebagai gantinya penyalinan sebanyak mungkin.

  2. Jika struktur data akan tetap bertahan selama keseluruhan operasi, sebaiknya melakukan pra-alokasi memori menggunakan tensor sementara. Anda mungkin perlu menggunakan OpData struct untuk mereferensikan indeks tensor dalam fungsi lain. Lihat contoh dalam kernel untuk konvolusi. Berikut adalah contoh cuplikan kode.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. Jika tidak menghabiskan terlalu banyak memori yang terbuang, lebih baik menggunakan ukuran tetap statis array (atau std::vector yang telah dialokasikan sebelumnya di Resize), bukan menggunakan std::vector yang dialokasikan secara dinamis pada setiap iterasi eksekusi.

  4. Hindari membuat instance template container library standar yang belum ada, karena mereka memengaruhi ukuran biner. Misalnya, jika Anda ingin std::map dalam operasi Anda yang tidak ada di kernel lain, menggunakan std::vector dengan pemetaan pengindeksan langsung dapat berfungsi sekaligus mempertahankan {i>binary size<i} kecil. Lihat kernel lain apa yang digunakan untuk mendapatkan insight (atau bertanya).

  5. Periksa pointer ke memori yang ditampilkan oleh malloc. Jika pointer ini nullptr, tidak ada operasi yang harus dilakukan menggunakan pointer tersebut. Jika Anda malloc dalam fungsi dan mengalami error keluar, batalkan alokasi memori sebelum Anda keluar.

  6. Gunakan TF_LITE_OPAQUE_ENSURE(context, condition) untuk memeriksa . Kode Anda tidak boleh membuat memori menggantung saat TF_LITE_OPAQUE_ENSURE digunakan, yaitu makro ini harus digunakan sebelum sumber daya apa pun yang dialokasikan yang akan bocor.