Özel operatörler

LiteRT yerleşik operatör kitaplığı yalnızca sınırlı sayıda TensorFlow operatörünü desteklediğinden her model dönüştürülemez. Ayrıntılı bilgi için operatör uyumluluğu başlıklı makaleyi inceleyin.

Dönüşüme izin vermek için kullanıcılar, LiteRT'de desteklenmeyen bir TensorFlow operatörünün kendi özel uygulamasını (özel operatör olarak bilinir) sağlayabilir. Bunun yerine, bir dizi desteklenmeyen (veya desteklenen) TensorFlow operatörünü tek bir birleştirilmiş optimize edilmiş özel operatörde birleştirmek istiyorsanız operatör birleştirme bölümüne bakın.

Özel operatör kullanma dört adımdan oluşur.

TensorFlow'da desteklenen ancak LiteRT'de desteklenmeyen özel operatör tf.atan (Atan olarak adlandırılır, TensorFlow modeli oluşturma bölümüne bakın) ile model çalıştırmanın uçtan uca bir örneğini inceleyelim.

TensorFlow Text operatörü, özel operatör örneğidir. Kod örneği için TF Metnini LiteRT'ye Dönüştürme eğitimine bakın.

Örnek: Özel Atan operatörü

LiteRT'nin sahip olmadığı bir TensorFlow operatörünü destekleme örneğini inceleyelim. Atan operatörünü kullandığımızı ve y = atan(x + offset) işlevi için çok basit bir model oluşturduğumuzu varsayalım. Bu durumda offset eğitilebilir.

TensorFlow modeli oluşturma

Aşağıdaki kod snippet'i, basit bir TensorFlow modelini eğitir. Bu model yalnızca Atan adlı bir özel operatör içerir. Bu operatör, y = atan(x + offset) işlevidir ve offset eğitilebilir.

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

Bu noktada, varsayılan dönüştürücü işaretleriyle bir LiteRT modeli oluşturmaya çalışırsanız aşağıdaki hata mesajını alırsınız:

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

LiteRT modeline dönüştürme

Dönüştürücü özelliğini allow_custom_ops aşağıda gösterildiği gibi ayarlayarak özel operatörler içeren bir LiteRT modeli oluşturun:

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

Bu noktada, aşağıdaki gibi komutlar kullanarak varsayılan yorumlayıcıyla çalıştırırsanız:

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

Hatayı almaya devam ediyorsanız:

Encountered unresolved custom op: Atan.

Operatörü oluşturun ve kaydedin.

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

LiteRT özel operatörleri, opak bir tür (TfLiteOperator) ve ilgili işlevlerden oluşan basit bir saf C API'si kullanılarak tanımlanır.

TfLiteOperator opak bir türdür:

typedef struct TfLiteOperator TfLiteOperator;

TfLiteOperator, operatörün kimliğini ve uygulamasını saklar. (Operatörün, operatörü çağıran düğümler için LiteRT grafiği düğümlerinde depolanan işlenenlerinden farklı olduğunu unutmayın.)

Bu türün örnekleri TfLiteOperatorCreate çağrılarıyla oluşturulur ve TfLiteOperatorDelete çağrılarıyla yok edilebilir.

Operatörün kimliği, oluşturucu işlevine yönelik parametreler aracılığıyla ayarlanır TfLiteOperatorCreate:

TfLiteOperator*
TfLiteOperatorCreate(
    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.
);

Operatör uygulaması, aşağıdaki imzalarla "yöntemler" tanımlayabilir. Bu yöntemlerin tümü isteğe bağlıdır ancak bir operatörün başarıyla değerlendirilebilmesi için operatör uygulamasının en az Prepare ve Invoke yöntemlerini tanımlaması ve ayarlaması (setter işlevlerini kullanarak) gerekir.

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

TF Lite özel işlemler API'si yalnızca adreslerini kullanacağından, işlem uygulamanızdaki işlev adlarının (veya C++ için ad alanı önekleri) yukarıdaki kod snippet'indeki işlev adlarıyla eşleşmesi gerekmez. Bunları anonim bir ad alanında veya statik işlevler olarak bildirmenizi öneririz.

Ancak operatör adınızı bu işlev adlarında ad alanı veya önek olarak eklemeniz önerilir:

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.
      

Bu bir C API'si olduğundan, bu "yöntemler" TfLiteOperator türünde C işlev işaretçileri olarak uygulanır. Bu işaretçiler, uygulama işlevlerinizin adresleri karşılık gelen ayarlayıcı işlevlere TfLiteOperatorSetMethodName iletilerek ayarlanır:

void TfLiteOperatorSetInit(
    TfLiteOperator* operator,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteOperatorSetFree(
    TfLiteOperator* operator,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
    TfLiteOperator* operator,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
    TfLiteOperator* operator,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
    TfLiteOperator* operator,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

TfLiteContext ve TfLiteNode ile ilgili ayrıntılar için common.h başlıklı makaleyi inceleyin. TfLiteContext, hata raporlama olanakları ve tüm tensörler dahil olmak üzere genel nesnelere erişim sağlar. TfLiteNode, operatör uygulamalarının giriş ve çıkışlarına erişmesine olanak tanır.

Yorumlayıcı bir modeli yüklediğinde, grafikteki her düğüm için Init() yöntemini bir kez çağırır. Bir işlem grafikte birden çok kez kullanılıyorsa belirli bir Init() birden fazla kez çağrılır. Özel işlemler için, parametre adlarını değerleriyle eşleyen bir flexbuffer içeren bir yapılandırma arabelleği sağlanır. Yorumlayıcı, işlem parametrelerini zaten ayrıştırdığı için yerleşik işlemlerin arabelleği boştur. Durum gerektiren çekirdek uygulamaları, durumu burada başlatmalı ve sahipliği arayan kullanıcıya aktarmalıdır. Her Init() çağrısı için Free()'e karşılık gelen bir çağrı olur. Bu sayede uygulamalar, Init()'de ayırmış olabilecekleri arabelleği kullanımdan kaldırabilir.

Giriş tensörleri yeniden boyutlandırıldığında yorumlayıcı, grafiği inceleyerek uygulamalara değişiklik hakkında bildirim gönderir. Bu sayede, dahili arabelleklerini yeniden boyutlandırabilir, giriş şekillerinin ve türlerinin geçerliliğini kontrol edebilir ve çıkış şekillerini yeniden hesaplayabilirler. Bu işlemlerin tümü Prepare() yöntemiyle yapılır ve uygulamalar, TfLiteOpaqueNodeGetUserData(node) kullanarak durumlarına erişebilir.

Son olarak, her çıkarım çalıştığında yorumlayıcı, Invoke() yöntemini çağırarak grafiği geçer ve burada da durum TfLiteOpaqueNodeGetUserData(node) olarak kullanılabilir.

Özel işlemler, bu "yöntem" işlevleri tanımlanarak ve ardından TfLiteOperator örneğini döndüren bir işlev tanımlanarak uygulanabilir. Bu işlev, TfLiteOperatorCreate ve ardından ilgili ayarlayıcı yöntemler çağrılarak oluşturulur:

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 TfLiteOperator* MyCustomOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* my_custom_op = ()[] {
        TfLiteOperator* r =
            TfLiteOperatorCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteOperatorSetInit(r, Init);
        TfLiteOperatorSetFree(r, Free);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* MyCustomOpCreate() {
  const TfLiteOperator* r =
      TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteOperatorSetInit(r, MyCustomOpInit);
  TfLiteOperatorSetFree(r, MyCustomOpFree);
  TfLiteOperatorSetPrepare(r, MyCustomOpPrepare);
  TfLiteOperatorSetInvoke(r, MyCustomOpEval);
  return r;
}

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

Kaydın otomatik olmadığını ve MyCustomOperator işlevinize açık bir çağrı yapılması gerektiğini unutmayın (ayrıntıları aşağıda bulabilirsiniz). BuiltinOpResolver (:builtin_ops hedefinden kullanılabilir) yerleşiklerin kaydını yaparken özel işlemlerin ayrı özel kitaplıklarda toplanması gerekir.

LiteRT çalışma zamanında çekirdeği tanımlama

LiteRT'de op'u kullanmak için yapmamız gereken tek şey iki işlev (Prepare ve Eval) ve bir TfLiteOperator oluşturmak için üçüncü bir işlev tanımlamaktır:

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 TfLiteOperator* AtanOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* atan_op = ()[] {
        auto* r = TfLiteOperatorCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* AtanOpCreate() {
  TfLiteOperator* r = TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteOperatorSetPrepare(r, Prepare);
  TfLiteOperatorSetInvoke(r, Eval);
  return r;
}

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

OpResolver başlatılırken özel işlemi çözümleyiciye ekleyin (örnek için aşağıya bakın). Bu işlem, operatörü LiteRT'ye kaydeder. Böylece LiteRT, yeni uygulamayı kullanabilir.

İşlemi çekirdek kitaplığına kaydetme

Şimdi işletmeciyi çekirdek kitaplığına kaydetmemiz gerekiyor. Bu işlem, OpResolver ile yapılır. Yorumlayıcı, arka planda, modeldeki her operatörü yürütmek üzere atanacak bir çekirdek kitaplığı yükler. Varsayılan kitaplık yalnızca yerleşik çekirdekleri içerse de özel kitaplık operatörleriyle değiştirilebilir/artırılabilir.

Operatör kodlarını ve adlarını gerçek koda çeviren OpResolver sınıfı şu şekilde tanımlanır:

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

Geriye dönük uyumluluk için bu sınıfın, opak tür TfLiteOperator yerine eski somut tür TfLiteRegistration kullandığını ancak TfLiteRegistration yapısının TfLiteOperator* türünde bir registration_external alanı içerdiğini unutmayın.

MutableOpResolver ve BuiltinOpResolver sınıfları OpResolver'den türetilir:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddAll(const MutableOpResolver& other);
  ...
};

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

Normal kullanım (özel işlemler olmadan) için BuiltinOpResolver kullanmanız ve şunları yazmanız gerekir:

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

Yukarıda oluşturulan özel işlemi eklemek için bunun yerine MutableOpResolver kullanabilir ve tflite::AddOp işlevini çağırabilirsiniz (çözücüyü InterpreterBuilder işlevine iletmeden önce):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());

Yerleşik işlemlerin çok büyük olduğu düşünülürse belirli bir işlem alt kümesine göre yeni bir OpResolver kodu oluşturulabilir. Bu alt küme, muhtemelen yalnızca belirli bir modelde bulunan işlemleri içerir. Bu, TensorFlow'un seçici kaydına eşdeğerdir (ve basit bir sürümü tools dizininde mevcuttur).

Özel operatörlerinizi Java'da tanımlamak istiyorsanız şu anda kendi özel JNI katmanınızı oluşturmanız ve kendi AAR'nızı bu jni kodunda derlemeniz gerekir. Benzer şekilde, Python'da kullanılabilen bu operatörleri tanımlamak isterseniz kayıtlarınızı Python sarmalayıcı koduna yerleştirebilirsiniz.

Tek bir operatör yerine bir dizi işlemi desteklemek için yukarıdakine benzer bir sürecin izlenebileceğini unutmayın. İhtiyacınız kadar AddCustom operatörü eklemeniz yeterlidir. Ayrıca, MutableOpResolver, AddBuiltin kullanarak yerleşiklerin uygulamalarını geçersiz kılmanıza da olanak tanır.

Operatörünüzü test etme ve profilleme

LiteRT karşılaştırma aracıyla işleminizin profilini oluşturmak için LiteRT'ye yönelik karşılaştırma modeli aracını kullanabilirsiniz. Test amacıyla, LiteRT'nin yerel derlemenizi AddCustom çağrısını (yukarıda gösterildiği gibi) register.cc dosyasına ekleyerek özel işleminizden haberdar edebilirsiniz.

En iyi uygulamalar

  1. Bellek ayırma ve ayırmama işlemlerini dikkatli bir şekilde optimize edin. Belleği Prepare içinde ayırmak Invoke içinde ayırmaktan daha verimlidir ve belleği döngüden önce ayırmak her yinelemede ayırmaktan daha iyidir. Kendiniz malloc yapmak yerine geçici tensör verilerini kullanın (2. maddeye bakın). Mümkün olduğunca çok sayıda kopyalama yapmak yerine işaretçiler/referanslar kullanın.

  2. Bir veri yapısı tüm işlem boyunca kalıcı olacaksa geçici tensörler kullanarak belleği önceden ayırmanızı öneririz. Diğer işlevlerde tensör dizinlerine referansta bulunmak için bir OpData yapısı kullanmanız gerekebilir. Evrişim için çekirdek bölümündeki örneğe bakın. Örnek bir kod snippet'i aşağıda verilmiştir.

    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. Çok fazla bellek israfına yol açmıyorsa her yürütme yinelemesinde dinamik olarak ayrılmış std::vector kullanmak yerine statik sabit boyutlu bir dizi (veya Resize içinde önceden ayrılmış bir std::vector) kullanmayı tercih edin.

  4. İkili boyutunu etkilediği için, henüz mevcut olmayan standart kitaplık kapsayıcı şablonlarını oluşturmaktan kaçının. Örneğin, işleminizde diğer çekirdeklerde bulunmayan bir std::map öğesine ihtiyacınız varsa doğrudan dizin oluşturma eşlemesi olan bir std::vector kullanmak, ikili boyutunu küçük tutarken işe yarayabilir. Diğer çekirdeklerin hangi bilgileri kullandığını öğrenerek bilgi edinin (veya soru sorun).

  5. malloc tarafından döndürülen belleğe yönelik işaretçiyi kontrol edin. Bu işaretçi nullptr ise bu işaretçi kullanılarak herhangi bir işlem yapılmamalıdır. Bir fonksiyonda malloc ve hata çıkışı varsa çıkmadan önce belleğin tahsisini kaldırın.

  6. Belirli bir koşulu kontrol etmek için TF_LITE_OPAQUE_ENSURE(context, condition) uygulamasını kullanın. Kodunuz, TF_LITE_OPAQUE_ENSURE kullanıldığında belleği askıda bırakmamalıdır. Diğer bir deyişle, bu makrolar, sızıntıya neden olacak kaynaklar ayrılmadan önce kullanılmalıdır.