Operatori personalizzati

Poiché la libreria operatori integrata di TensorFlow Lite supporta solo un numero limitato di operatori TensorFlow, non tutti i modelli sono convertibili. Per maggiori dettagli, consulta la compatibilità degli operatori.

Per consentire la conversione, gli utenti possono fornire la propria implementazione personalizzata di un operatore TensorFlow non supportato in TensorFlow Lite, noto come operatore personalizzato. Se, invece, vuoi combinare una serie di operatori TensorFlow non supportati (o supportati) in un unico operatore personalizzato e ottimizzato, consulta la sezione Fuso dell'operatore.

L'utilizzo degli operatori personalizzati prevede quattro passaggi.

Esaminiamo un esempio end-to-end dell'esecuzione di un modello con un operatore personalizzato tf.atan (denominato Atan, consulta la sezione Creare un modello TensorFlow), che è supportato in TensorFlow, ma non in TensorFlow Lite.

L'operatore di testo TensorFlow è un esempio di operatore personalizzato. Consulta il tutorial Conversione di testo TF in TF Lite per un esempio di codice.

Esempio: operatore Atan personalizzato

Vediamo un esempio del supporto di un operatore TensorFlow di cui TensorFlow Lite non dispone. Supponiamo di utilizzare l'operatore Atan e di creare un modello molto semplice per una funzione y = atan(x + offset), in cui è possibile addestrare offset.

Crea un modello TensorFlow

Lo snippet di codice riportato di seguito addestra un modello TensorFlow semplice. Questo modello contiene solo un operatore personalizzato denominato Atan, che è una funzione y = atan(x + offset), in cui è possibile addestrare offset.

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

A questo punto, se provi a generare un modello TensorFlow Lite con i flag del convertitore predefiniti, riceverai il seguente messaggio di errore:

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

Converti in un modello TensorFlow Lite

Crea un modello TensorFlow Lite con operatori personalizzati, impostando l'attributo di conversione allow_custom_ops come mostrato di seguito:

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

A questo punto, se lo esegui con l'interprete predefinito utilizzando comandi come seguenti:

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

Riceverai ancora l'errore:

Encountered unresolved custom op: Atan.

Crea e registra l'operatore.

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

Gli operatori personalizzati di TensorFlow Lite vengono definiti utilizzando un'API pur-C semplice composta da un tipo opaco (TfLiteRegistrationExternal) e dalle funzioni correlate.

TfLiteRegistrationExternal è un tipo opaco:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal archivia l'identità e l'implementazione dell'operatore. Tieni presente che l'operatore è diverso dai suoi operandi, archiviati nei nodi grafici di TF Lite per i nodi che chiamano l'operatore.

Le istanze di questo tipo vengono create con chiamate a TfLiteRegistrationExternalCreate e possono essere eliminate chiamando TfLiteRegistrationExternalDelete.

L'identità dell'operatore viene impostata tramite i parametri nella funzione del costruttore 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.
);

L'implementazione dell'operatore può definire "metodi" con le seguenti firme. Tutti questi metodi sono facoltativi, ma affinché un operatore possa essere valutato correttamente, l'implementazione dell'operatore deve definire e impostare (utilizzando le funzioni setter) almeno i metodi Prepare e 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);

I names delle funzioni (o i prefissi dello spazio dei nomi per C++) nell'implementazione op non devono corrispondere ai nomi delle funzioni nello snippet di codice riportato sopra, poiché l'API TF Lite Custom Ops utilizzerà solo i relativi indirizzi. Ti consigliamo invece di dichiararle in uno spazio dei nomi anonimo o come funzioni statiche.

Tuttavia, è consigliabile includere il nome dell'operatore come spazio dei nomi o prefisso nei nomi di queste funzioni:

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.
      

Poiché questa è un'API C, questi "metodi" vengono implementati come puntatori a funzione C nel tipo TfLiteRegistrationExternal, che vengono impostati passando gli indirizzi delle funzioni di implementazione alle funzioni setter corrispondenti 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));

Consulta la pagina common.h per i dettagli su TfLiteContext e TfLiteNode. TfLiteContext offre funzionalità di segnalazione degli errori e accesso agli oggetti globali, inclusi tutti i tensori. TfLiteNode consente alle implementazioni dell'operatore di accedere ai propri input e output.

Quando l'interprete carica un modello, chiama il metodo Init() una volta per ogni nodo nel grafico. Un determinato Init() verrà chiamato più di una volta se l'operazione viene utilizzata più volte nel grafico. Per le operazioni personalizzate verrà fornito un buffer di configurazione, contenente un flexbuffer che mappa i nomi dei parametri ai relativi valori. Il buffer è vuoto per le operazioni integrate perché l'interprete ha già analizzato i parametri operativi. Le implementazioni del kernel che richiedono uno stato dovrebbero inizializzarlo qui e trasferire la proprietà al chiamante. Per ogni chiamata Init(), sarà disponibile una chiamata corrispondente a Free(), che consentirà alle implementazioni di eliminare il buffer che potrebbero aver allocato in Init().

Ogni volta che i tensori di input vengono ridimensionati, l'interprete analizza il grafico per notificare le implementazioni della modifica. Ciò offre la possibilità di ridimensionare il buffer interno, verificare la validità delle forme e dei tipi di input e ricalcolare le forme di output. Per farlo, è necessario il metodo Prepare() e le implementazioni possono accedere al loro stato utilizzando TfLiteOpaqueNodeGetUserData(node).

Infine, ogni volta che viene eseguita l'inferenza, l'interprete attraversa il grafico che chiama il metodo Invoke() e anche in questo caso lo stato è disponibile come TfLiteOpaqueNodeGetUserData(node).

Le operazioni personalizzate possono essere implementate definendo queste funzioni "metodo" e definendo una funzione che restituisca un'istanza di TfLiteRegistrationExternal creata chiamando TfLiteRegistrationExternalCreate e successivamente i metodi setter pertinenti:

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

Tieni presente che la registrazione non è automatica ed è necessario effettuare una chiamata esplicita alla tua funzione MyCustomOpRegistration (vedi i dettagli di seguito). Mentre lo standard BuiltinOpResolver (disponibile dalla destinazione :builtin_ops) si occupa della registrazione degli elementi integrati, le operazioni personalizzate dovranno essere raccolte in librerie personalizzate separate.

Definizione del kernel nel runtime di TensorFlow Lite

Per utilizzare il comando op in TensorFlow Lite, è sufficiente definire due funzioni (Prepare e Eval) e una terza per costruire 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;
}
      

Durante l'inizializzazione di OpResolver, aggiungi l'operazione personalizzata nel resolver (vedi di seguito per un esempio). L'operatore viene registrato con Tensorflow Lite, in modo che TensorFlow Lite possa utilizzare la nuova implementazione. Tieni presente che gli ultimi due argomenti in TfLiteRegistration corrispondono alle funzioni AtanPrepare e AtanEval che hai definito per l'operazione personalizzata. Se hai utilizzato le funzioni AtanInit e AtanFree per inizializzare le variabili utilizzate nell'operazione e per liberare spazio, rispettivamente, verranno aggiunte ai primi due argomenti di TfLiteRegistration; in questo esempio, questi argomenti sono impostati su nullptr.

Registra l'operatore con la libreria del kernel

Ora dobbiamo registrare l'operatore nella libreria kernel. Questo viene fatto con un OpResolver. Dietro le quinte, l'interprete caricherà una libreria di kernel che verranno assegnati per eseguire ciascuno degli operatori del modello. Sebbene la libreria predefinita contenga solo kernel integrati, è possibile sostituirla/incrementarla con operatori operativi di libreria personalizzati.

La classe OpResolver, che converte i codici e i nomi degli operatori nel codice effettivo, è definita come segue:

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

Tieni presente che, per la compatibilità con le versioni precedenti, questa classe utilizza il tipo di cemento precedente TfLiteRegistration anziché il tipo opaco TfLiteRegistrationExternal, ma lo struct TfLiteRegistration contiene un campo registration_external di tipo TfLiteRegistrationExternal*.

Le classi MutableOpResolver e BuiltinOpResolver derivano da 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.
};

L'utilizzo regolare (senza operazioni personalizzate) richiede l'uso di BuiltinOpResolver e la scrittura:

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

Per aggiungere l'operazione personalizzata creata sopra, puoi utilizzare invece un MutableOpResolver e chiamare AddCustom (prima di passare il resolver a InterpreterBuilder):

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

Se il set di operazioni integrate è considerato troppo grande, potrebbe essere generato un nuovo codice OpResolver in base a un determinato sottoinsieme di operazioni, possibilmente solo quelle contenute in un determinato modello. Questa è l'equivalente della registrazione selettiva di TensorFlow (e una versione semplice è disponibile nella directory tools).

Se vuoi definire i tuoi operatori personalizzati in Java, devi creare un livello JNI personalizzato e compilare il tuo AAR in questo codice jni. Allo stesso modo, se vuoi definire questi operatori disponibili in Python, puoi inserire le registrazioni nel codice wrapper in Python.

Tieni presente che puoi seguire una procedura simile a quella indicata sopra per supportare un insieme di operazioni anziché un singolo operatore. Basta aggiungere tutti gli operatori AddCustom che ti servono. Inoltre, MutableOpResolver ti consente anche di eseguire l'override delle implementazioni delle funzionalità integrate utilizzando AddBuiltin.

Testa e profila il tuo operatore

Per profilare la tua operazione con lo strumento di benchmark di TensorFlow Lite, puoi utilizzare lo strumento per i modelli di benchmark per TensorFlow Lite. A scopo di test, puoi rendere la tua build locale di TensorFlow Lite consapevole della tua operazione personalizzata aggiungendo la chiamata AddCustom appropriata (come mostrato sopra) a register.cc

best practice

  1. Ottimizza con cautela le allocazioni della memoria e le disallocazione. L'allocazione della memoria in Prepare è più efficiente che in Invoke e l'allocazione della memoria prima di un loop è migliore rispetto a ogni iterazione. Usa dati di tensori temporanei invece di maltrattarti (vedi l'elemento 2). Usa puntatori/riferimenti anziché copiare il più possibile.

  2. Se una struttura di dati persisterà durante l'intera operazione, consigliamo di preallocare la memoria utilizzando tensori temporanei. Potresti dover utilizzare uno struct OpData per fare riferimento agli indici tensori in altre funzioni. Vedi l'esempio nel kernel per la convoluzione. Di seguito è riportato uno snippet di codice di esempio.

    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. Se lo spreco di memoria non è eccessivo, preferisci utilizzare un array di dimensioni fisse statico (o un elemento std::vector preallocato in Resize) anziché un std::vector allocato dinamicamente per ogni iterazione di esecuzione.

  4. Evita di creare un'istanza di modelli di container di librerie standard che non esistono già, poiché influiscono sulle dimensioni binarie. Ad esempio, se nell'operazione hai bisogno di un elemento std::map che non esiste in altri kernel, l'utilizzo di std::vector con mapping di indicizzazione diretta potrebbe funzionare mantenendo ridotte le dimensioni binarie. Scopri cosa usano gli altri kernel per ottenere insight (o chiedi).

  5. Controlla il puntatore alla memoria restituita da malloc. Se il puntatore è nullptr, non è necessario eseguire alcuna operazione. Se malloc in una funzione esce da un errore, distribuisci la memoria prima di uscire.

  6. Usa TF_LITE_OPAQUE_ENSURE(context, condition) per verificare una condizione specifica. Il codice non deve lasciare la memoria inutilizzata quando viene utilizzato TF_LITE_OPAQUE_ENSURE, ovvero queste macro devono essere utilizzate prima che venga allocata qualsiasi risorsa che dovesse perdere.