Operator khusus

Karena pustaka operator bawaan TensorFlow Lite hanya mendukung operator TensorFlow dalam jumlah terbatas, tidak semua model dapat dikonversi. Untuk detailnya, lihat kompatibilitas operator .

Untuk memungkinkan konversi, pengguna dapat menyediakan implementasi kustom mereka sendiri untuk operator TensorFlow yang tidak didukung di TensorFlow Lite, yang dikenal sebagai operator kustom. Jika sebaliknya, Anda ingin menggabungkan serangkaian operator TensorFlow yang tidak didukung (atau didukung) menjadi satu operator kustom yang dioptimalkan dan digabungkan, lihat penggabungan operator .

Menggunakan operator khusus terdiri dari empat langkah.

Mari kita lihat contoh menyeluruh dalam menjalankan model dengan operator khusus tf.atan (dinamai Atan , lihat #create_a_tensorflow_model) yang didukung di TensorFlow, namun tidak didukung di TensorFlow Lite.

Operator Teks TensorFlow adalah contoh operator khusus. Lihat tutorial Konversi Teks TF ke TF Lite untuk contoh kode.

Contoh: Operator Atan Kustom

Mari kita lihat contoh dukungan operator TensorFlow yang tidak dimiliki TensorFlow Lite. Asumsikan kita menggunakan operator Atan dan kita sedang membangun model yang sangat sederhana untuk suatu fungsi y = atan(x + offset) , di mana offset dapat dilatih.

Buat Model TensorFlow

Cuplikan kode berikut melatih model TensorFlow sederhana. Model ini hanya berisi operator khusus bernama Atan , yang merupakan fungsi y = atan(x + offset) , yang 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 TensorFlow Lite dengan flag konverter default, Anda akan mendapatkan pesan error berikut:

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

Konversikan ke Model TensorFlow Lite

Buat model TensorFlow Lite dengan operator khusus, dengan menyetel 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 titik ini, jika Anda menjalankannya dengan juru bahasa default menggunakan perintah seperti berikut:

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

Anda masih akan mendapatkan kesalahan:

Encountered unresolved custom op: Atan.

Buat dan daftarkan operator.

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

Operator khusus TensorFlow Lite ditentukan menggunakan API C murni sederhana yang terdiri dari tipe buram ( TfLiteRegistrationExternal ) dan fungsi terkait.

TfLiteRegistrationExternal adalah tipe buram:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal menyimpan identitas dan implementasi operator. (Perhatikan bahwa operator berbeda dari operannya, yang disimpan dalam node grafik TF Lite untuk node yang memanggil operator.)

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

Identitas operator diatur 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 mendefinisikan "metode" dengan tanda tangan berikut. Semua metode ini bersifat opsional, tetapi agar operator berhasil dievaluasi, implementasi operator perlu mendefinisikan dan menyetel (menggunakan fungsi penyetel) 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 API operasi kustom TF Lite hanya akan menggunakan alamatnya. Memang kami menyarankan Anda mendeklarasikannya dalam namespace anonim atau sebagai fungsi statis.

Namun sebaiknya sertakan nama operator Anda sebagai namespace atau awalan pada nama fungsi berikut:

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 penunjuk fungsi C dalam tipe TfLiteRegistrationExternal , yang disetel dengan meneruskan alamat fungsi implementasi Anda ke fungsi penyetel yang sesuai TfLiteRegistrationExternalSet MethodName :

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

Lihat common.h untuk detail tentang TfLiteContext dan TfLiteNode . TfLiteContext menyediakan fasilitas pelaporan kesalahan dan akses ke objek global, termasuk semua tensor. TfLiteNode memungkinkan implementasi operator mengakses input dan outputnya.

Saat interpreter memuat model, ia memanggil metode Init() satu kali untuk setiap node dalam grafik. Init() tertentu akan dipanggil lebih dari sekali jika operasi digunakan beberapa kali dalam grafik. Untuk operasi khusus, buffer konfigurasi akan disediakan, berisi flexbuffer yang memetakan nama parameter ke nilainya. Buffer kosong untuk operasi bawaan karena penerjemah 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 terkait ke Free() , yang memungkinkan implementasi membuang buffer yang mungkin telah mereka alokasikan di Init() .

Setiap kali tensor masukan diubah ukurannya, penerjemah akan melihat grafik yang memberitahukan implementasi perubahan tersebut. Hal ini memberi mereka kesempatan untuk mengubah ukuran buffer internal, memeriksa validitas bentuk dan tipe masukan, dan menghitung ulang bentuk keluaran. Ini semua dilakukan melalui metode Prepare() , dan implementasi dapat mengakses statusnya menggunakan TfLiteOpaqueNodeGetUserData(node) .

Terakhir, setiap kali inferensi dijalankan, penafsir melintasi grafik dengan memanggil metode Invoke() , dan di sini juga status tersedia sebagai TfLiteOpaqueNodeGetUserData(node) .

Operasi khusus dapat diimplementasikan dengan mendefinisikan fungsi "metode" tersebut, dan kemudian mendefinisikan fungsi yang mengembalikan instance TfLiteRegistrationExternal yang dibuat dengan memanggil TfLiteRegistrationExternalCreate dan kemudian metode penyetel yang relevan:

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 panggilan eksplisit ke fungsi MyCustomOpRegistration Anda harus dilakukan (lihat detailnya di bawah). Meskipun BuiltinOpResolver standar (tersedia dari target :builtin_ops ) menangani pendaftaran bawaan, operasi khusus harus dikumpulkan di perpustakaan khusus yang terpisah.

Mendefinisikan kernel di runtime TensorFlow Lite

Yang perlu kita lakukan untuk menggunakan operasi di TensorFlow Lite adalah mendefinisikan dua fungsi ( Prepare dan Eval ), dan fungsi 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(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast(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(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast(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 operasi khusus ke dalam solver (lihat contoh di bawah). Tindakan ini akan mendaftarkan operator ke Tensorflow Lite sehingga TensorFlow Lite dapat menggunakan implementasi baru. Perhatikan bahwa dua argumen terakhir di TfLiteRegistration sesuai dengan fungsi AtanPrepare dan AtanEval yang Anda tetapkan untuk operasi kustom. Jika Anda menggunakan fungsi AtanInit dan AtanFree untuk menginisialisasi variabel yang digunakan dalam operasi dan untuk mengosongkan ruang, maka variabel tersebut akan ditambahkan ke dua argumen pertama TfLiteRegistration ; argumen tersebut disetel ke nullptr dalam contoh ini.

Daftarkan operator dengan perpustakaan kernel

Sekarang kita perlu mendaftarkan operator ke perpustakaan kernel. Hal ini dilakukan dengan OpResolver . Di belakang layar, interpreter akan memuat perpustakaan kernel yang akan ditugaskan untuk mengeksekusi setiap operator dalam model. Meskipun perpustakaan default hanya berisi kernel bawaan, dimungkinkan untuk mengganti/menambahnya dengan operator operasi perpustakaan khusus.

Kelas OpResolver , yang menerjemahkan kode dan nama operator menjadi kode sebenarnya, didefinisikan seperti 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, kelas ini menggunakan tipe beton lama TfLiteRegistration daripada tipe buram TfLiteRegistrationExternal , tetapi struct TfLiteRegistration berisi bidang registration_external bertipe TfLiteRegistrationExternal* .

Kelas 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 khusus) mengharuskan Anda menggunakan BuiltinOpResolver dan menulis:

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

Untuk menambahkan operasi khusus yang dibuat di atas, Anda dapat menggunakan MutableOpResolver , dan memanggil AddCustom (sebelum Anda meneruskan penyelesai ke InterpreterBuilder ):

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

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

Jika Anda ingin menentukan operator khusus di Java, saat ini Anda perlu membuat lapisan JNI khusus Anda sendiri dan mengkompilasi AAR Anda sendiri dalam kode jni ini . Demikian pula, jika Anda ingin mendefinisikan operator yang tersedia dalam Python, Anda dapat menempatkan registrasi Anda di kode pembungkus Python .

Perhatikan bahwa proses serupa seperti di atas dapat diikuti untuk mendukung serangkaian operasi, bukan hanya satu operator. Cukup tambahkan operator AddCustom sebanyak yang Anda perlukan. Selain itu, MutableOpResolver juga memungkinkan Anda mengganti implementasi bawaan dengan menggunakan AddBuiltin .

Uji dan buat profil operator Anda

Untuk membuat profil operasi Anda dengan alat benchmark TensorFlow Lite, Anda dapat menggunakan alat model benchmark untuk TensorFlow Lite. Untuk tujuan pengujian, Anda dapat membuat versi lokal TensorFlow Lite mengetahui operasi kustom Anda dengan menambahkan panggilan AddCustom yang sesuai (seperti yang ditunjukkan di atas) ke 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 loop lebih baik daripada di setiap iterasi. Gunakan data tensor sementara daripada melakukan mallocing sendiri (lihat item 2). Gunakan petunjuk/referensi daripada menyalin sebanyak mungkin.

  2. Jika struktur data akan bertahan selama seluruh operasi, kami menyarankan untuk mengalokasikan memori terlebih dahulu menggunakan tensor sementara. Anda mungkin perlu menggunakan struct OpData untuk mereferensikan indeks tensor di fungsi lain. Lihat contoh di kernel untuk konvolusi . Cuplikan kode contoh ada di bawah.

    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 array ukuran tetap statis (atau std::vector yang telah dialokasikan sebelumnya di Resize ) daripada menggunakan std::vector yang dialokasikan secara dinamis setiap iterasi eksekusi.

  4. Hindari membuat instance templat kontainer perpustakaan standar yang belum ada, karena akan memengaruhi ukuran biner. Misalnya, jika Anda memerlukan std::map dalam operasi Anda yang tidak ada di kernel lain, menggunakan std::vector dengan pemetaan pengindeksan langsung dapat berfungsi sambil menjaga ukuran biner tetap kecil. Lihat apa yang digunakan kernel lain untuk mendapatkan wawasan (atau tanyakan).

  5. Periksa penunjuk ke memori yang dikembalikan oleh malloc . Jika penunjuk ini adalah nullptr , tidak ada operasi yang boleh dilakukan menggunakan penunjuk tersebut. Jika Anda malloc dalam suatu fungsi dan mengalami kesalahan keluar, batalkan alokasi memori sebelum Anda keluar.

  6. Gunakan TF_LITE_OPAQUE_ENSURE(context, condition) untuk memeriksa kondisi tertentu. Kode Anda tidak boleh membiarkan memori menggantung ketika TF_LITE_OPAQUE_ENSURE digunakan, yaitu makro ini harus digunakan sebelum sumber daya dialokasikan yang akan bocor.