Operatorzy celni

Ponieważ wbudowana biblioteka operatorów TensorFlow Lite obsługuje tylko ograniczoną liczbę operatorów TensorFlow, nie każdy model można konwertować. Aby uzyskać szczegółowe informacje, zobacz kompatybilność operatorów .

Aby umożliwić konwersję, użytkownicy mogą zapewnić własną, niestandardową implementację nieobsługiwanego operatora TensorFlow w TensorFlow Lite, znanego jako operator niestandardowy. Jeśli zamiast tego chcesz połączyć serię nieobsługiwanych (lub obsługiwanych) operatorów TensorFlow w jeden połączony, zoptymalizowany operator niestandardowy, zapoznaj się z łączeniem operatorów .

Korzystanie z operatorów niestandardowych składa się z czterech kroków.

Przeanalizujmy kompleksowy przykład uruchomienia modelu z niestandardowym operatorem tf.atan (o nazwie Atan , patrz #create_a_tensorflow_model), który jest obsługiwany w TensorFlow, ale nie jest obsługiwany w TensorFlow Lite.

Operator TensorFlow Text jest przykładem operatora niestandardowego. Zobacz samouczek Konwertuj tekst TF na TF Lite, aby zapoznać się z przykładem kodu.

Przykład: niestandardowy operator Atan

Przeanalizujmy przykład obsługi operatora TensorFlow, którego nie ma w TensorFlow Lite. Załóżmy, że używamy operatora Atan i budujemy bardzo prosty model funkcji y = atan(x + offset) , gdzie offset można wytrenować.

Utwórz model TensorFlow

Poniższy fragment kodu uczy prostego modelu TensorFlow. Ten model zawiera po prostu niestandardowy operator o nazwie Atan , który jest funkcją y = atan(x + offset) , gdzie offset można wytrenować.

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

W tym momencie, jeśli spróbujesz wygenerować model TensorFlow Lite z domyślnymi flagami konwertera, pojawi się następujący komunikat o błędzie:

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

Konwertuj na model TensorFlow Lite

Utwórz model TensorFlow Lite z niestandardowymi operatorami, ustawiając atrybut konwertera allow_custom_ops , jak pokazano poniżej:

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

W tym momencie, jeśli uruchomisz go z domyślnym interpreterem za pomocą poleceń takich jak następujące:

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

Nadal będziesz otrzymywać błąd:

Encountered unresolved custom op: Atan.

Utwórz i zarejestruj operatora.

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

Operatory niestandardowe TensorFlow Lite są definiowane przy użyciu prostego interfejsu API w czystym języku C, który składa się z nieprzezroczystego typu ( TfLiteRegistrationExternal ) i powiązanych funkcji.

TfLiteRegistrationExternal jest typem nieprzezroczystym:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal przechowuje tożsamość operatora i implementację. (Zauważ, że operator różni się od jego operandów, które są przechowywane w węzłach wykresu TF Lite dla węzłów wywołujących operatora.)

Instancje tego typu są konstruowane za pomocą wywołań TfLiteRegistrationExternalCreate i można je zniszczyć, wywołując TfLiteRegistrationExternalDelete .

Tożsamość operatora ustawiana jest poprzez parametry funkcji konstruktora 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.
);

Implementacja operatora może definiować „metody” za pomocą następujących sygnatur. Wszystkie te metody są opcjonalne, ale aby operator mógł zostać pomyślnie oceniony, implementacja operatora musi zdefiniować i ustawić (przy użyciu funkcji ustawiających) przynajmniej metody Prepare i 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);

Nazwy funkcji (lub przedrostki przestrzeni nazw w przypadku C++) w implementacji op nie muszą odpowiadać nazwom funkcji w powyższym fragmencie kodu, ponieważ interfejs API niestandardowych operacji TF Lite będzie używał tylko ich adresów. Rzeczywiście zalecamy zadeklarowanie ich w anonimowej przestrzeni nazw lub jako funkcje statyczne.

Dobrym pomysłem jest jednak dołączenie nazwy operatora jako przestrzeni nazw lub przedrostka w nazwach funkcji:

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.
      

Ponieważ jest to interfejs API języka C, te „metody” są implementowane jako wskaźniki funkcji C typu TfLiteRegistrationExternal , które są ustawiane poprzez przekazanie adresów funkcji implementacyjnych do odpowiednich funkcji ustawiających 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));

Szczegółowe informacje na temat TfLiteContext i TfLiteNode można znaleźć w common.h . TfLiteContext zapewnia funkcje raportowania błędów i dostęp do obiektów globalnych, w tym wszystkich tensorów. TfLiteNode umożliwia implementacjom operatorów dostęp do ich wejść i wyjść.

Gdy interpreter ładuje model, wywołuje metodę Init() raz dla każdego węzła na wykresie. Dana Init() zostanie wywołana więcej niż raz, jeśli op zostanie użyta wielokrotnie na wykresie. W przypadku operacji niestandardowych zostanie udostępniony bufor konfiguracyjny zawierający bufor elastyczny, który odwzorowuje nazwy parametrów na ich wartości. Bufor jest pusty dla wbudowanych operacji, ponieważ interpreter przeanalizował już parametry operacji. Implementacje jądra wymagające stanu powinny go tutaj zainicjować i przenieść własność na obiekt wywołujący. Każdemu wywołaniu Init() odpowiada wywołanie Free() , umożliwiając implementacjom pozbycie się buforu, który mogły przydzielić w Init() .

Za każdym razem, gdy zmieniany jest rozmiar tensorów wejściowych, interpreter przegląda wykres powiadamiając o implementacjach zmiany. Daje im to możliwość zmiany rozmiaru wewnętrznego bufora, sprawdzenia ważności kształtów i typów wejściowych oraz ponownego obliczenia kształtów wyjściowych. Wszystko to odbywa się za pomocą metody Prepare() , a implementacje mogą uzyskać dostęp do swojego stanu za pomocą TfLiteOpaqueNodeGetUserData(node) .

Na koniec, przy każdym uruchomieniu wnioskowania interpreter przechodzi przez wykres, wywołując metodę Invoke() i tutaj również stan jest dostępny jako TfLiteOpaqueNodeGetUserData(node) .

Niestandardowe operacje można zaimplementować, definiując te funkcje „metody”, a następnie definiując funkcję zwracającą instancję TfLiteRegistrationExternal skonstruowaną przez wywołanie TfLiteRegistrationExternalCreate , a następnie odpowiednie metody ustawiające:

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

Należy pamiętać, że rejestracja nie jest automatyczna i należy wykonać wyraźne wywołanie funkcji MyCustomOpRegistration (szczegóły poniżej). Podczas gdy standardowy BuiltinOpResolver (dostępny w celu :builtin_ops ) zajmuje się rejestracją wbudowanych operacji, niestandardowe operacje będą musiały być gromadzone w oddzielnych niestandardowych bibliotekach.

Definiowanie jądra w środowisku wykonawczym TensorFlow Lite

Wszystko, co musimy zrobić, aby użyć opcji w TensorFlow Lite, to zdefiniować dwie funkcje ( Prepare i Eval ) i trzecią, aby skonstruować 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;
}
      

Podczas inicjowania OpResolver dodaj niestandardową opcję do modułu rozpoznawania nazw (przykład poniżej). Spowoduje to zarejestrowanie operatora w Tensorflow Lite, dzięki czemu TensorFlow Lite będzie mógł korzystać z nowej implementacji. Należy zauważyć, że dwa ostatnie argumenty w TfLiteRegistration odpowiadają funkcjom AtanPrepare i AtanEval zdefiniowanym dla operacji niestandardowej. Jeśli użyłeś funkcji AtanInit i AtanFree odpowiednio do inicjalizacji zmiennych używanych w operacji i do zwolnienia miejsca, wówczas zostaną one dodane do pierwszych dwóch argumentów TfLiteRegistration ; w tym przykładzie argumenty te mają wartość nullptr .

Zarejestruj operatora w bibliotece jądra

Teraz musimy zarejestrować operatora w bibliotece jądra. Odbywa się to za pomocą OpResolver . Za kulisami interpreter załaduje bibliotekę jąder, które zostaną przypisane do wykonania każdego z operatorów w modelu. Chociaż domyślna biblioteka zawiera tylko wbudowane jądra, możliwe jest jej zastąpienie/rozszerzenie za pomocą niestandardowych operatorów operacji z biblioteki.

Klasę OpResolver , która tłumaczy kody i nazwy operatorów na rzeczywisty kod, definiuje się w następujący sposób:

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

Należy zauważyć, że w celu zapewnienia zgodności z poprzednimi wersjami ta klasa używa starszego typu konkretnego TfLiteRegistration zamiast nieprzezroczystego typu TfLiteRegistrationExternal , ale struktura TfLiteRegistration zawiera pole registration_external typu TfLiteRegistrationExternal* .

Klasy MutableOpResolver BuiltinOpResolver wywodzą się z 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.
};

Regularne użycie (bez niestandardowych operacji) wymaga użycia BuiltinOpResolver i napisania:

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

Aby dodać niestandardową operację utworzoną powyżej, możesz zamiast tego użyć MutableOpResolver i wywołać AddCustom (zanim przekażesz funkcję rozpoznawania nazw do InterpreterBuilder ):

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

Jeśli zestaw wbudowanych operacji zostanie uznany za zbyt duży, nowy OpResolver może zostać wygenerowany w kodzie na podstawie danego podzbioru operacji, prawdopodobnie tylko tych zawartych w danym modelu. Jest to odpowiednik rejestracji selektywnej TensorFlow (a jej prosta wersja jest dostępna w katalogu tools ).

Jeśli chcesz zdefiniować swoje niestandardowe operatory w Javie, obecnie musisz zbudować własną niestandardową warstwę JNI i skompilować własny plik AAR w tym kodzie jni . Podobnie, jeśli chcesz zdefiniować te operatory dostępne w Pythonie, możesz umieścić swoje rejestracje w kodzie opakowania Pythona .

Należy zauważyć, że podobny proces jak powyżej można zastosować do obsługi zestawu operacji zamiast pojedynczego operatora. Po prostu dodaj tyle operatorów AddCustom , ile potrzebujesz. Ponadto MutableOpResolver umożliwia także przesłonięcie implementacji wbudowanych przy użyciu AddBuiltin .

Przetestuj i sprofiluj swojego operatora

Aby sprofilować swoją operację za pomocą narzędzia porównawczego TensorFlow Lite, możesz użyć narzędzia modelu porównawczego dla TensorFlow Lite. Do celów testowych możesz poinformować lokalną wersję TensorFlow Lite o niestandardowej operacji, dodając odpowiednie wywołanie AddCustom (jak pokazano powyżej) do pliku Register.cc

Najlepsze praktyki

  1. Ostrożnie optymalizuj alokację i dealokację pamięci. Alokacja pamięci w Prepare jest bardziej wydajna niż w Invoke , a alokacja pamięci przed pętlą jest lepsza niż w każdej iteracji. Używaj tymczasowych danych tensorowych zamiast mallocować (patrz punkt 2). Używaj wskaźników/odniesień zamiast kopiować tak dużo, jak to możliwe.

  2. Jeśli struktura danych będzie się utrzymywać przez całą operację, zalecamy wstępne przydzielenie pamięci za pomocą tymczasowych tensorów. Może być konieczne użycie struktury OpData do odwoływania się do indeksów tensora w innych funkcjach. Zobacz przykład w jądrze dla splotu . Przykładowy fragment kodu znajduje się poniżej.

    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. Jeśli nie kosztuje to zbyt dużo zmarnowanej pamięci, wolisz używać statycznej tablicy o stałym rozmiarze (lub wstępnie przydzielonej std::vector w Resize ) zamiast używać dynamicznie przydzielanej std::vector w każdej iteracji wykonania.

  4. Unikaj tworzenia instancji standardowych szablonów kontenerów bibliotek, które jeszcze nie istnieją, ponieważ wpływają one na rozmiar binarny. Na przykład, jeśli potrzebujesz std::map w swojej operacji, która nie istnieje w innych jądrach, użycie std::vector z bezpośrednim mapowaniem indeksującym może działać, utrzymując mały rozmiar binarny. Zobacz, czego używają inne jądra, aby uzyskać wgląd (lub zapytaj).

  5. Sprawdź wskaźnik do pamięci zwróconej przez malloc . Jeśli ten wskaźnik to nullptr , nie należy wykonywać żadnych operacji przy użyciu tego wskaźnika. Jeżeli wykonasz malloc w funkcji i wystąpi błąd przy wyjściu, zwolnij pamięć przed wyjściem.

  6. Użyj TF_LITE_OPAQUE_ENSURE(context, condition) , aby sprawdzić konkretny warunek. Twój kod nie może pozostawiać pamięci zawieszonej, gdy używane jest TF_LITE_OPAQUE_ENSURE , tj. te makra powinny być użyte przed alokacją jakichkolwiek zasobów, które ulegną wyciekowi.