Benutzerdefinierte Operatoren

Da die integrierte LiteRT-Operator-Bibliothek nur eine nicht jedes Modell ist konvertierbar. Weitere Informationen Weitere Informationen zur Operatorkompatibilität

Um Conversions zu ermöglichen, können Nutzer ihre eigene benutzerdefinierte Implementierung eines nicht unterstützter TensorFlow-Operator in LiteRT (benutzerdefinierter Operator). Falls Sie stattdessen eine Reihe nicht unterstützter oder TensorFlow-Operatoren in einen einzelnen kombinierten, optimierten benutzerdefinierten Operator zusammengefasst, siehe Operator Fusion aus.

Die Verwendung benutzerdefinierter Operatoren umfasst vier Schritte.

Sehen wir uns ein Beispiel für die Ausführung eines Modells mit einem benutzerdefinierten Operator tf.atan (namens Atan, siehe TensorFlow-Modell erstellen), der wird in TensorFlow unterstützt, in LiteRT jedoch nicht.

Der TensorFlow-Operator „Text“ ist ein Beispiel für einen benutzerdefinierten Operator. Weitere Informationen finden Sie in der Anleitung zum Konvertieren von TF Text in LiteRT mit einem Codebeispiel.

Beispiel: Benutzerdefinierter Atan-Operator

Sehen wir uns ein Beispiel für die Unterstützung eines TensorFlow-Operators an, bei dem LiteRT nicht. Angenommen, wir verwenden den Atan-Operator erstellen wir ein sehr einfaches Modell für die Funktion y = atan(x + offset), wobei offset ist trainierbar.

TensorFlow-Modell erstellen

Mit dem folgenden Code-Snippet wird ein einfaches TensorFlow-Modell trainiert. Dieses Modell hat enthält einen benutzerdefinierten Operator mit dem Namen Atan. Dies ist eine Funktion y = atan(x + offset), bei der offset trainierbar ist.

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

Wenn Sie versuchen, ein LiteRT-Modell mit der Standardeinstellung Konverter-Flags erhalten Sie die folgende Fehlermeldung:

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

In ein LiteRT-Modell umwandeln

LiteRT-Modell mit benutzerdefinierten Operatoren durch Festlegen des Converters erstellen Attribut allow_custom_ops wie unten gezeigt:

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

Wenn Sie es mit dem Standard-Interpreter ausführen und Befehle wie folgt:

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

Die Fehlermeldung wird weiterhin angezeigt:

Encountered unresolved custom op: Atan.

Erstellen und registrieren Sie den Operator.

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

Benutzerdefinierte LiteRT-Operatoren werden mit einem einfachen, reinen C-API definiert, besteht aus einem intransparenten Typ (TfLiteRegistrationExternal) und verwandten Funktionen.

TfLiteRegistrationExternal ist ein opaker Typ:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal speichert die Identität und Implementierung des Betreibers. (Beachten Sie, dass sich der Operator von seinen Operanden unterscheidet, die im LiteRT-Grafikknoten für Knoten, die den Operator aufrufen.)

Instanzen dieses Typs werden mit Aufrufen TfLiteRegistrationExternalCreate und kann durch den Aufruf gelöscht werden. TfLiteRegistrationExternalDelete.

Die Identität des Operators wird über die Parameter für die Konstruktorfunktion festgelegt. 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.
);

Die Operatorimplementierung kann „Methoden“ definieren mit den folgenden Signaturen. Alle diese Methoden sind optional, aber muss die Operatorimplementierung mithilfe des Setters Funktionen) mindestens die Methoden Prepare und 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);

die Namen der Funktion (oder Namespace-Präfixe für C++) in der Vorgangsimplementierung nicht mit den Funktionsnamen im obigen Code-Snippet übereinstimmen, Für die Lite Custom Operations API werden nur die Adressen verwendet. Wir empfehlen Ihnen sogar, Sie deklarieren sie in einem anonymen Namespace oder als statische Funktionen.

Es ist jedoch eine gute Idee, den Operatornamen als Namespace oder Präfix folgenden Funktionsnamen:

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.
      

Da es sich um eine C API handelt, werden als C-Funktionszeiger in den Typ TfLiteRegistrationExternal, die festgelegt werden, indem die Adressen der Ihre Implementierungsfunktionen mit den entsprechenden Setter-Funktionen 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));

Weitere Informationen finden Sie unter common.h findest du weitere Informationen zu TfLiteContext und TfLiteNode. TfLiteContext gibt einen Fehler zurück und Zugriff auf globale Objekte, einschließlich aller Tensoren. TfLiteNode ermöglicht Operatorimplementierungen den Zugriff auf ihre Ein- und Ausgaben.

Wenn der Interpreter ein Modell lädt, wird die Methode Init() jeweils einmal aufgerufen. Knoten im Diagramm. Ein bestimmtes Init() wird mehr als einmal aufgerufen, wenn der Vorgang in der Grafik mehrfach verwendet wird. Bei benutzerdefinierten Vorgängen ist ein Konfigurationspuffer bereitgestellt, die einen Flex-Zwischenspeicher enthält, der Parameternamen ihren Werten zuordnet. Die Für integrierte Operationen ist der Zwischenspeicher leer, da der Interpreter den op-Parameter. Kernelimplementierungen, die einen Status erfordern, sollten ihn initialisieren und die Eigentümerschaft an den Anrufer übertragen. Für jeden Init()-Aufruf werden einen entsprechenden Aufruf von Free(), mit dem Implementierungen die Zwischenspeicher, den er möglicherweise in Init() zugewiesen hat.

Immer wenn die Größe der Eingabetensoren geändert wird, durchläuft der Interpreter die Diagramm, das Implementierungen der Änderung informiert. Das gibt ihnen die Möglichkeit, die Größe des internen Zwischenspeichers ändern, die Gültigkeit von Eingabeformen und -typen überprüfen und die Ausgabeformen neu zu berechnen. Dies erfolgt alles über die Methode Prepare(). Implementierungen auf ihren Status zugreifen können, TfLiteOpaqueNodeGetUserData(node).

Jedes Mal, wenn eine Inferenz ausgeführt wird, durchläuft der Interpreter schließlich den Graph, Methode Invoke(). Auch hier ist der Status als TfLiteOpaqueNodeGetUserData(node).

Benutzerdefinierte Vorgänge können implementiert werden, indem diese "Methode" definiert wird. Funktionen und dann Definieren einer Funktion, die eine Instanz von TfLiteRegistrationExternal zurückgibt konstruiert, indem TfLiteRegistrationExternalCreate und dann die entsprechende Setter-Methoden:

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

Die Registrierung erfolgt nicht automatisch und ist ein expliziter Aufruf an Ihre MyCustomOpRegistration-Funktion festgelegt werden (siehe Details unten). Während die Standard-BuiltinOpResolver (verfügbar über das Ziel :builtin_ops) verwendet Registrierung von builtins, benutzerdefinierte Operationen müssen in benutzerdefinierte Bibliotheken.

Kernel in der LiteRT-Laufzeit definieren

Um den op in LiteRT zu verwenden, müssen wir nur zwei Funktionen definieren. (Prepare und Eval) und ein drittes zum Erstellen von 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;
}
      

Fügen Sie beim Initialisieren von OpResolver den benutzerdefinierten Vorgang in den Resolver (siehe unten ein Beispiel). Dadurch wird der Betreiber bei LiteRT registriert, damit LiteRT die neue Implementierung verwenden kann. Beachten Sie, dass die letzten beiden Argumente in TfLiteRegistration entsprechen AtanPrepare und AtanEval die Sie für den benutzerdefinierten Vorgang definiert haben. Wenn Sie AtanInit und AtanFree verwendet haben zum Initialisieren von Variablen, die im Vorgang verwendet werden, und um Speicherplatz freizugeben, werden sie den ersten beiden Argumenten TfLiteRegistration; Diese Argumente sind in diesem Beispiel auf nullptr gesetzt.

Operator bei der Kernel-Bibliothek registrieren

Jetzt müssen wir den Operator bei der Kernel-Bibliothek registrieren. Dies geschieht mit ein OpResolver. Im Hintergrund lädt der Dolmetscher eine Bibliothek mit Kernels, die für die Ausführung der einzelnen Operatoren im Modell zugewiesen werden. Die Standardbibliothek enthält zwar nur integrierte Kernel, es ist jedoch möglich, durch benutzerdefinierte Bibliotheksoperatoren ersetzen/erweitern.

Die Klasse OpResolver, die Operatorcodes und -namen in tatsächliche wie folgt definiert:

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

Beachten Sie, dass diese Klasse aus Gründen der Abwärtskompatibilität den älteren konkreten Typ verwendet. TfLiteRegistration statt TfLiteRegistrationExternal des Typs „undurchsichtig“, Die Struktur TfLiteRegistration enthält jedoch ein registration_external-Feld mit Geben Sie TfLiteRegistrationExternal* ein.

Die Klassen MutableOpResolver und BuiltinOpResolver werden abgeleitet aus 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.
};

Für die normale Verwendung (ohne benutzerdefinierte Vorgänge) muss die BuiltinOpResolver verwendet werden. und schreibe:

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

Sie können stattdessen MutableOpResolver verwenden, um die oben erstellte benutzerdefinierte Operation hinzuzufügen. und rufe AddCustom auf (bevor du den Resolver an den InterpreterBuilder):

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

Wenn die Gruppe von integrierten Vorgängen als zu groß erachtet wird, könnte ein neuer OpResolver Code basierend auf einer bestimmten Teilmenge von Operationen generiert, möglicherweise nur in einem Modell an. Dies entspricht der selektiven Registrierung von TensorFlow (Eine einfache Version davon ist im Verzeichnis tools verfügbar.)

Wenn Sie Ihre benutzerdefinierten Operatoren in Java definieren möchten, müssen Sie derzeit Erstellen Sie Ihre eigene JNI-Schicht und kompilieren Sie eigene AAE. in diesem Jni-Code. Wenn Sie diese in Python verfügbaren Operatoren definieren möchten, können Sie registrieren Sie sich im Python-Wrapper-Code

Ein ähnlicher Prozess wie oben kann verwendet werden, um eine Reihe von Operationen statt mit einem einzelnen Operator. Fügen Sie einfach so viele AddCustom-Operatoren hinzu je nach Bedarf anpassen. Außerdem können Sie mit MutableOpResolver Implementierungen von builtins mithilfe von AddBuiltin.

Operator testen und Profil erstellen

Um mit dem LiteRT-Benchmark-Tool ein Profil für Ihre Operation zu erstellen, können Sie das Benchmark-Modell-Tool für LiteRT. Zu Testzwecken können Sie Ihren lokalen Build aus LiteRT kennt Ihre benutzerdefinierte Operation durch Hinzufügen des entsprechenden AddCustom anrufen (wie oben gezeigt) bei register.cc

Best Practices

  1. Optimieren Sie Arbeitsspeicherzuweisungen und ‐zuweisungen vorsichtig. Arbeitsspeicher wird zugewiesen in Prepare ist effizienter als in Invoke und es wird Arbeitsspeicher zugewiesen vor einer Schleife besser ist als in jeder Iteration. Temporäre Tensor-Daten verwenden statt sich selbst zu verkaufen (siehe Punkt 2). Stattdessen Verweise/Verweise verwenden so viel wie möglich zu kopieren.

  2. Wenn eine Datenstruktur während des gesamten Vorgangs bestehen bleibt, empfehlen wir, Vorabzuweisung des Arbeitsspeichers mithilfe temporärer Tensoren. Möglicherweise müssen Sie ein OpData-Struktur, um auf die Tensor-Indizes in anderen Funktionen zu verweisen. Weitere Informationen finden Sie in der Beispiel im Kernel for Convolution Im Folgenden finden Sie ein Beispiel für ein Code-Snippet.

    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. Wenn dies nicht zu viel vergeudeten Arbeitsspeicher verursacht, sollten Sie lieber eine statische feste Größe verwenden. Array (oder eine vorab zugewiesene std::vector in Resize) anstelle eines std::vector wird bei jeder Ausführungsdurchlauf dynamisch zugewiesen.

  4. Instanziieren von Containervorlagen für Standardbibliotheken, die noch nicht geschehen sind, vermeiden da sie sich auf die Binärgröße auswirken. Wenn Sie zum Beispiel eine std::map in Ihrem Vorgang, der in anderen Kerneln nicht vorhanden ist, mit einem std::vector mit direkter Indexierungszuordnung könnte funktionieren, während der Binärgröße klein ist. Sehen Sie sich an, was andere Kernel verwenden, um Erkenntnisse zu gewinnen (oder fragen Sie nach).

  5. Prüfen Sie den Zeiger auf den von malloc zurückgegebenen Arbeitsspeicher. Wenn dieser Zeiger nullptr, sollten keine Vorgänge mit diesem Zeiger ausgeführt werden. Wenn Sie malloc in einer Funktion und haben einen Fehler-Exit. Weisen Sie Arbeitsspeicher auf, bevor Sie beenden.

  6. Mit TF_LITE_OPAQUE_ENSURE(context, condition) können Sie nach einem bestimmten . Ihr Code darf die Erinnerung nicht hängen lassen, wenn TF_LITE_OPAQUE_ENSURE wird verwendet, d.h. diese Makros sollten vor dem alle Ressourcen zugewiesen sind, die ein Datenleck verursachen.