Özel operatörler

TensorFlow Lite yerleşik operatör kitaplığı yalnızca sınırlı sayıda TensorFlow operatörünü desteklediğinden her model dönüştürülebilir değildir. Ayrıntılar için operatör uyumluluğu bölümüne bakın.

Kullanıcılar, dönüşüm sağlamak için TensorFlow Lite'ta desteklenmeyen TensorFlow operatörünün kendi özel uygulamasını sağlayabilir. Bunun yerine, desteklenmeyen (veya desteklenen) bir dizi TensorFlow operatörünü tek bir çok kaynaklı optimize edilmiş özel operatörde birleştirmek isterseniz operatör birleştirme bölümünü inceleyin.

Özel operatörleri kullanmak dört adımdan oluşur.

Bir modelin TensorFlow'da desteklenen ancak TensorFlow Lite'ta desteklenmeyen özel operatör tf.atan (Atan olarak adlandırılmıştır, TensorFlow Modeli Oluşturma başlıklı bölümü) ile çalıştırılmasıyla ilgili uçtan uca bir örneğin üzerinden geçelim.

TensorFlow Metin operatörü, özel operatöre bir örnektir. Kod örneği için TF Metnini TF Lite'a Dönüştürme eğiticisine bakın.

Örnek: Özel Atan operatörü

TensorFlow Lite'ta olmayan bir TensorFlow operatörünü destekleyen örneği adım adım inceleyelim. Atan operatörünü kullandığımızı ve y = atan(x + offset) işlevinin offset eğitilebilir olduğu çok basit bir model geliştirdiğimizi varsayalım.

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çeriyor. Bu operatör, offset özelliğinin eğitilebilir olduğu bir y = atan(x + offset) işlevidir.

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 TensorFlow Lite 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.

TensorFlow Lite Modeline dönüştür

allow_custom_ops dönüştürücü özelliğini aşağıda gösterildiği gibi ayarlayarak özel operatörlerle bir TensorFlow Lite 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, aracı aşağıdaki gibi komutları kullanarak varsayılan çevirmen ile çalıştırırsanız:

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

Şu hatayı yine alırsını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"

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

TfLiteRegistrationExternal opak bir tür:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal, operatörün kimliğini ve uygulamasını depolar. (Operatörün, operatörü çağıran düğümlerin TF Lite grafik düğümlerinde depolanan işlenenlerden farklı olduğunu unutmayın.)

Bu tür örnekler, TfLiteRegistrationExternalCreate çağrılarıyla oluşturulur ve TfLiteRegistrationExternalDelete çağrısıyla kaldırılabilir.

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

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

Operatör uygulaması, aşağıdaki imzalarla "yöntemleri" tanımlayabilir. Bu yöntemlerin tümü isteğe bağlıdır ancak bir operatörün başarılı bir şekilde değerlendirilebilmesi için operatör uygulamasının en azından Prepare ve Invoke yöntemlerini tanımlaması ve ayarlaması (belirleyici işlevleri kullanılarak) 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 işlev adlarını kullanacağından işlem uygulamanızdaki işlev names (veya C++ için ad alanı öneklerinin) yukarıdaki kod snippet'inde bulunan işlev adlarıyla eşleşmesine gerek yoktur. Gerçekten de bunları anonim bir ad alanında veya statik işlevler olarak bildirmenizi öneririz.

Ancak, operatör adınızı aşağıdaki işlev adlarına ad alanı veya önek olarak eklemek iyi bir fikirdir:

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" TfLiteRegistrationExternal türünde C işlev işaretçileri olarak uygulanır. Bu yöntemler, uygulama işlevlerinizin adreslerini ilgili setter işlevlerine TfLiteRegistrationExternalSetMethodName geçirerek ayarlanır:

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

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

Çevirmen bir model yüklediğinde, grafikteki her düğüm için Init() yöntemini bir kez çağırır. Belirli bir Init(), işlem grafikte birden fazla kez kullanılırsa 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. Çevirmen, op parametrelerini zaten ayrıştırdığından, yerleşik işlemler için arabellek boş olur. Durum gerektiren çekirdek uygulamaları bunu burada başlatmalı ve sahipliği arayana aktarmalıdır. Her Init() çağrısı için Free() öğesine karşılık gelen bir çağrı olur. Bu çağrı, uygulamaların Init() içinde ayırmış olabilecekleri tamponu atmasını sağlar.

Giriş tensörleri yeniden boyutlandırıldığında yorumlayıcı, değişikliğin uygulamalarını bildiren grafiği inceler. Bu sayede dahili tamponlarını yeniden boyutlandırma, giriş şekillerinin ve türlerinin geçerliliğini kontrol etme ve çıktı şekillerini yeniden hesaplama şansına sahip olurlar. Bu işlemlerin tamamı Prepare() yöntemiyle gerçekleştirilir ve uygulamalar, TfLiteOpaqueNodeGetUserData(node) kullanarak durumlarına erişebilir.

Son olarak, çıkarım her çalıştığında çevirmen, Invoke() yöntemini çağıran grafiği aktarır ve burada durum TfLiteOpaqueNodeGetUserData(node) olarak sunulur.

Özel işlemler, bu "yöntem" işlevlerini tanımlayıp ardından TfLiteRegistrationExternalCreate ve ardından ilgili belirleyici yöntemler çağırarak oluşturulan TfLiteRegistrationExternal örneğini döndüren bir işlev tanımlayarak uygulanabilir:

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

Kaydın otomatik olmadığını ve MyCustomOpRegistration işlevinize açık bir çağrı yapılması gerektiğini unutmayın (aşağıdaki ayrıntılara bakın). Standart BuiltinOpResolver (:builtin_ops hedefinden kullanılabilir) yerleşiklerin kaydını halleder ancak özel işlemlerin ayrı özel kitaplıklarda toplanması gerekir.

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

TensorFlow Lite'ta işlemi kullanmak için tek yapmamız gereken iki işlev (Prepare ve Eval) ve üçüncüsü de bir TfLiteRegistrationExternal oluşturmaktı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(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;
}
      

OpResolver başlatılırken özel işlemi çözümleyiciye ekleyin (aşağıdaki örneği inceleyin). Bu işlem, TensorFlow Lite'ın yeni uygulamayı kullanabilmesi için operatörü Tensorflow Lite'a kaydeder. TfLiteRegistration içindeki son iki bağımsız değişkenin, özel işlem için tanımladığınız AtanPrepare ve AtanEval işlevlerine karşılık geldiğini unutmayın. İşlemde kullanılan değişkenleri başlatmak ve yer açmak için AtanInit ve AtanFree işlevlerini kullandıysanız sırasıyla bunlar TfLiteRegistration öğesinin ilk iki bağımsız değişkenine eklenir. Söz konusu bağımsız değişkenler bu örnekte nullptr olarak ayarlanmıştır.

Operatörü çekirdek kitaplığına kaydetme

Şimdi operatörü çekirdek kitaplığına kaydetmemiz gerekiyor. Bu işlem bir OpResolver ile gerçekleştirilir. Çevirmen, sahne arkasında modeldeki operatörlerin her birini yürütmek için atanacak çekirdeklerden oluşan bir kitaplık yükler. Varsayılan kitaplık yalnızca yerleşik çekirdekler içerse de özel kitaplık işlem operatörleriyle değiştirebilir/geliştirmek mümkündür.

Operatör kodlarını ve adlarını gerçek koda dönüştüren OpResolver sınıfı aşağıdaki gibi 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 TfLiteRegistrationExternal opak türü yerine eski TfLiteRegistration beton türünü kullandığını ancak TfLiteRegistration yapısının TfLiteRegistrationExternal* türünde bir registration_external alanı içerdiğini unutmayın.

MutableOpResolver ve BuiltinOpResolver sınıfları, OpResolver parametresinden türetilir:

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.
};

Normal kullanım (özel işlemler olmadan) BuiltinOpResolver kullanmanızı ve şunu yazmanızı gerektirir:

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

Yukarıda oluşturulan özel işlemi eklemek için bunun yerine bir MutableOpResolver kullanabilir ve AddCustom yöntemini çağırabilirsiniz (çözümleyiciyi InterpreterBuilder'a iletmeden önce):

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

Yerleşik işlem grubunun çok büyük olduğu kabul edilirse belirli bir işlem alt kümesine (muhtemelen yalnızca belirli bir modelde bulunanlara) göre yeni bir OpResolver kod oluşturulabilir. Bu, TensorFlow'un seçmeli kaydıyla eşdeğerdir (ve basit bir sürümünü tools dizininde bulabilirsiniz).

Java'da özel operatörlerinizi tanımlamak istiyorsanız şu anda kendi özel JNI katmanınızı oluşturmanız ve bu jni kodunda kendi AAR'nizi derlemeniz gerekir. Benzer şekilde, bu operatörleri Python'da 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 uygulanabileceğini unutmayın. İhtiyaç duyduğunuz sayıda AddCustom operatörü ekleyebilirsiniz. Buna ek olarak MutableOpResolver, AddBuiltin kullanarak yerleşik uygulama uygulamalarını geçersiz kılmanıza da olanak tanır.

Operatörünüzü test etme ve profil oluşturma

TensorFlow Lite karşılaştırma aracıyla işlem profilinizi oluşturmak için TensorFlow Lite'ın karşılaştırma modeli aracını kullanabilirsiniz. Test amacıyla, register.cc öğesine uygun AddCustom çağrısını (yukarıda gösterildiği gibi) ekleyerek TensorFlow Lite'ın yerel derlemenizin özel işleminizden haberdar olmasını sağlayabilirsiniz.

En iyi uygulamalar

  1. Bellek ayırma ve ayırma işlemlerini dikkatli bir şekilde optimize edin. Prepare bölgesinde bellek ayırmak Invoke ürününden daha etkilidir ve bir döngüden önce bellek ayırmak her yinelemeden daha iyidir. Kendinizi normalleştirmek yerine geçici tensör verilerini kullanın (2. maddeye bakın). Mümkün olduğu kadar kopyalamak yerine işaretçiler/referanslar kullanın.

  2. Bir veri yapısı işlem boyunca kalıcı olacaksa geçici tensörler kullanarak belleğin önceden ayrılmasını öneririz. Diğer işlevlerdeki tensör dizinlerine referans vermek için bir OpData struct kullanmanız gerekebilir. Kıvrım için çekirdek'teki ö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. Bellek açısından çok fazla boşluğa neden olmuyorsa her yürütme yinelemesinde dinamik olarak ayrılmış bir std::vector kullanmak yerine statik bir sabit boyut dizisi (veya Resize içinde önceden ayrılmış bir std::vector) kullanmayı tercih edin.

  4. İkili dosya boyutunu etkilediği için mevcut olmayan standart kitaplık kapsayıcısı şablonlarını örneklendirmekten kaçının. Örneğin, işleminizde diğer çekirdeklerde bulunmayan bir std::map'ye ihtiyacınız varsa doğrudan dizine ekleme eşlemesine sahip bir std::vector kullanmak ikili boyutu küçük tutarken işe yarayabilir. Diğer çekirdeklerin bilgi edinmek (veya soru sormak) için neler kullandığını görün.

  5. malloc tarafından döndürülen hafıza işaretçisini kontrol edin. Bu işaretçi nullptr ise bu işaretçi kullanılarak hiçbir işlem yapılmamalıdır. Bir işlevin içinde malloc yer alırsa ve çıkışta hata alırsanız çıkmadan önce belleği ayırın.

  6. Belirli bir durumu kontrol etmek için TF_LITE_OPAQUE_ENSURE(context, condition) kullanın. Kodunuz, TF_LITE_OPAQUE_ENSURE kullanıldığında belleğin askıda kalmasına izin vermemelidir. Diğer bir deyişle, bu makrolar sızıntı yapacak herhangi bir kaynak tahsis edilmeden önce kullanılmalıdır.