Operatory niestandardowe

Wbudowana biblioteka operatorów LiteRT obsługuje tylko liczba operatorów TensorFlow, nie każdy model można konwertować. Więcej informacji: znajdziesz w artykule o zgodności operatorów.

Aby umożliwić konwersję, użytkownicy mogą wprowadzić własną, niestandardową implementację nieobsługiwany operator TensorFlow w LiteRT, znany jako operator niestandardowy. Jeśli zamiast tego chcesz połączyć serię nieobsługiwanych (lub obsługiwanych) operatory TensorFlow w jednym uśrednionym zoptymalizowanym operatorze niestandardowym; zapoznaj się z operator fusing.

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

Przyjrzyjmy się kompleksowemu przykładowi uruchamiania modelu z niestandardowym tf.atan (o nazwie Atan, zapoznaj się z artykułem Tworzenie modelu TensorFlow), który jest obsługiwany w TensorFlow, ale nie jest obsługiwany w LiteRT.

Przykładem operatora niestandardowego jest operator TensorFlow Text. Zobacz Samouczek konwertowania tekstu TF na LiteRT przedstawiający przykładowy kod.

Przykład: niestandardowy operator Atan

Przyjrzyjmy się przykładowi obsługi operatora TensorFlow, który LiteRT nie ma. Załóżmy, że używamy operatora Atan i że tworzymy bardzo prosty model funkcji y = atan(x + offset), w której Węzeł offset można wytrenować.

Tworzenie modelu TensorFlow

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

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

Jeśli w tym momencie spróbujesz wygenerować model LiteRT z domyślnym modelem flagi 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 LiteRT

Utwórz model LiteRT z operatorami niestandardowymi, ustawiając konwerter atrybut allow_custom_ops jak poniżej:

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

Jeśli teraz uruchomisz ją za pomocą domyślnego tłumacza przy użyciu poleceń takich jak następujące:

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

Nadal pojawia się błąd:

Encountered unresolved custom op: Atan.

Utwórz i zarejestruj operator.

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

Niestandardowe operatory LiteRT są definiowane za pomocą prostego interfejsu API czystego C, składa się z typu nieprzezroczystego (TfLiteRegistrationExternal) i powiązanych funkcji.

TfLiteRegistrationExternal jest typem nieprzezroczystym:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal przechowuje tożsamość operatora i implementację. (Uwaga: ten operator różni się od jego operandów, które są przechowywane w Węzły wykresu LiteRT dla węzłów, które wywołują operator).

Instancje tego typu są tworzone z wywołaniami TfLiteRegistrationExternalCreate i można ją zniszczyć, wywołując TfLiteRegistrationExternalDelete

Tożsamość operatora jest ustawiana za pomocą parametrów w 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 z użyciem operatorów może definiować „metody” z następującymi podpisami. Wszystkie te metody są opcjonalne, ale dla operatora operator musi określić i ustawić (za pomocą metody ustawiającej) funkcji) co najmniej metod 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 prefiksy przestrzeni nazw w przypadku C++) w Twojej implementacji operacji; nie muszą odpowiadać nazwom funkcji z powyższego fragmentu kodu, ponieważ Interfejs Lite Custom Ops API będzie używać tylko ich adresów. Naprawdę zalecamy zadeklarować je w anonimowej przestrzeni nazw lub jako funkcje statyczne.

Warto jednak umieścić nazwę operatora jako przestrzeń nazw lub prefiks te nazwy 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 w języku C, te „metody” są zaimplementowane jako wskaźniki funkcji C w typu TfLiteRegistrationExternal, które są ustawiane przez przekazanie adresów funkcji implementacji do odpowiednich funkcji ustawiających TfLiteRegistrationExternalSetMethodName:

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

Więcej informacji: common.h aby dowiedzieć się więcej o TfLiteContext i TfLiteNode. TfLiteContext zawiera błąd oraz dostęp do obiektów globalnych, w tym wszystkich tensorów. TfLiteNode umożliwia implementacji operatorów dostęp do danych wejściowych i wyjściowych.

Gdy interpreter wczytuje model, wywołuje metodę Init() raz dla każdej z nich. na wykresie. Wartość Init() zostanie wywołana więcej niż raz, jeśli op jest używane wielokrotnie na wykresie. W przypadku operacji niestandardowych bufor konfiguracji będzie z funkcją elastycznego bufora, która mapuje nazwy parametrów na ich wartości. bufor jest pusty dla operacji wbudowanych, ponieważ interpreter przeanalizował już parametry operacji. Implementacje jądra, które wymagają stanu, powinny je zainicjować i przenieś własność na rozmówcę. W przypadku każdego wywołania Init() nastąpi odpowiednie wywołanie Free(), dzięki czemu implementacje usuwają bufor, który mogli przydzielić w Init().

Po każdej zmianie rozmiaru tensorów wejściowych tłumacz wykres powiadamiający o zmianach. Dzięki temu mogą zmienić rozmiar wewnętrznego bufora, sprawdzić poprawność kształtów i typów danych wejściowych oraz i ponownie obliczyć kształty danych wyjściowych. Odbywa się to za pomocą metody Prepare(), implementacje mogą uzyskać dostęp do swojego stanu za pomocą TfLiteOpaqueNodeGetUserData(node)

Na koniec przy każdym uruchomieniu wnioskowania tłumacz przegląda wykres, metody Invoke() i tutaj stan jest dostępny jako TfLiteOpaqueNodeGetUserData(node)

Operacje niestandardowe można wdrażać poprzez zdefiniowanie tych „metod” funkcji, a następnie definiują funkcję, która zwraca instancję TfLiteRegistrationExternal utworzony przez wywołanie TfLiteRegistrationExternalCreate, a następnie odpowiednie metody ustawiania:

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

Pamiętaj, że rejestracja nie jest automatyczna i stanowi bezpośrednie wywołanie Należy wykonać funkcję MyCustomOpRegistration (szczegóły poniżej). Natomiast standardowa BuiltinOpResolver (dostępna z poziomu celu :builtin_ops) przyjmuje o rejestrację wbudowanych komponentów, niestandardowe operacje będą musiały być zbierane oddzielne biblioteki niestandardowe.

Definiowanie jądra w środowisku wykonawczym LiteRT

Aby korzystać z op w LiteRT, wystarczy zdefiniować 2 funkcje, (Prepare i Eval), a trzeci do utworzenia TfLiteRegistrationExternal:

C++

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

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

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

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

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

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

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

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

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

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

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

C

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

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

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

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

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

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

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

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

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

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

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

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

Podczas inicjowania interfejsu OpResolver dodaj niestandardowe działanie do resolvera (zobacz poniżej. Spowoduje to zarejestrowanie operatora w LiteRT, więc że LiteRT może użyć nowej implementacji. Pamiętaj, że ostatnie dwa argumenty w funkcji TfLiteRegistration odpowiadają AtanPrepare i AtanEval funkcji zdefiniowanych na potrzeby operacji niestandardowych. Jeśli korzystasz z usług AtanInit i AtanFree do inicjowania zmiennych używanych w działaniu i zwalniania miejsca, to zostaną one dodane do pierwszych dwóch argumentów argumentu TfLiteRegistration; W tym przykładzie te argumenty są ustawione na nullptr.

Rejestrowanie operatora w bibliotece jądra

Teraz musimy zarejestrować operatora w bibliotece jądra. Odbywa się to za pomocą OpResolver. Za kulisami tłumacz wczyta bibliotekę jądra systemu, które zostaną przypisane do wykonywania poszczególnych operatorów w modelu. Chociaż biblioteka domyślna zawiera tylko wbudowane jądra, możliwe jest zastąpić/rozszerzyć ją niestandardowymi operatorami operacji biblioteki.

Klasa OpResolver, która przekłada kody i nazwy operatorów na rzeczywiste jest zdefiniowany w taki sposób:

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

Pamiętaj, że dla zapewnienia zgodności wstecznej ta klasa używa starszego typu konkretnego typu TfLiteRegistration zamiast nieprzezroczystego typu TfLiteRegistrationExternal, ale struktura TfLiteRegistration zawiera pole registration_external o wartości wpisz TfLiteRegistrationExternal*.

Klasy MutableOpResolver i BuiltinOpResolver pochodzą 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żywanie (bez niestandardowych operacji) wymaga używania interfejsu BuiltinOpResolver i wpisz:

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

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

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

Jeśli zestaw działań wbudowanych zostanie uznany za za duży, nowy OpResolver może zostać generowany na podstawie określonego podzbioru operacji, prawdopodobnie tylko w danym modelu. Jest to odpowiednik selektywnej rejestracji TensorFlow (a jej prosta wersja jest dostępna w katalogu tools).

Jeśli chcesz zdefiniować niestandardowe operatory w Javie, utwórz własną warstwę JNI i skompiluj własny AAR w tym kodzie Jni. Jeśli chcesz zdefiniować te operatory dostępne w Pythonie, możesz zarejestruj się w Kod otoki Pythona.

Możesz zastosować podobną procedurę, jak powyżej, aby obsługiwać zestaw a nie za pomocą jednego operatora. Dodaj tyle operatorów AddCustom według potrzeb. Dodatkowo MutableOpResolver umożliwia również zastąpienie implementacji wbudowanych za pomocą AddBuiltin.

Testowanie i profilowanie operatora

Aby profilować swoją działalność za pomocą narzędzia Test porównawczy LiteRT, możesz użyć narzędzie do analizy porównawczej w przypadku LiteRT. Na potrzeby testów możesz utworzyć lokalną kompilację LiteRT orientuje się w działaniach niestandardowych poprzez dodanie odpowiedniej wartości AddCustom. wywołaj (jak pokazano powyżej) do register.cc

Sprawdzone metody

  1. Ostrożnie optymalizuj przydziały pamięci i ich usuwanie. Przydzielam pamięć w trybie Prepare jest bardziej wydajny niż w Invoke oraz przez przydzielanie pamięci przed pętlą jest lepsza od każdej iteracji. Użyj danych tensorów tymczasowych niż się malować (patrz punkt 2). Zamiast tego użyj wskaźników/odwołań nie kopiując ich.

  2. Jeśli struktura danych pozostanie niezmieniona podczas całej operacji, zalecamy przez wstępne przydzielanie pamięci przy użyciu tensorów tymczasowych. Może być konieczne użycie Struktura OpData odwołująca się do indeksów tensorów w innych funkcjach. Zobacz w jądro do splotu. Poniżej znajduje się przykładowy fragment kodu.

    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 zbyt dużo pamięci, lepiej użyj statycznego, stałego rozmiaru tablica (lub wstępnie przydzielona tablica std::vector w Resize), zamiast używać dynamicznie przydzielał element std::vector w każdej iteracji wykonania.

  4. Unikaj tworzenia instancji szablonów kontenerów biblioteki standardowej, które jeszcze nie są tego typu ponieważ wpływają na rozmiar plików binarnych. Na przykład, jeśli potrzebujesz std::map w operacji, której nie ma w innych jądrach, przy użyciu metody std::vector z mapowaniem indeksowania bezpośredniego może działać przy zachowaniu małym rozmiarze binarnym. Zobacz, jak inne jądra wykorzystują do uzyskiwania statystyk (lub o to pytaj).

  5. Sprawdź wskaźnik do wspomnienia zwróconego przez funkcję malloc. Jeśli ten wskaźnik to nullptr, za pomocą tego wskaźnika nie należy wykonywać żadnych działań. Jeśli malloc jest w funkcji i ma wyjście błędu, zwolnij pamięć, zanim .

  6. Aby znaleźć konkretny kod, użyj kodu TF_LITE_OPAQUE_ENSURE(context, condition) . Kod nie może pozostawać w pamięci, gdy TF_LITE_OPAQUE_ENSURE, co oznacza, że tych makr należy użyć przed a jeśli zostaną przydzielone zasoby, które wycieknie,