Operatori personalizzati

Poiché la libreria di operatori integrata di TensorFlow Lite supporta solo un numero limitato di operatori TensorFlow, non tutti i modelli sono convertibili. Per i dettagli, fare riferimento alla 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 desideri combinare una serie di operatori TensorFlow non supportati (o supportati) in un unico operatore personalizzato ottimizzato con fusione, fai riferimento alla fusione degli operatori .

L'utilizzo degli operatori personalizzati prevede quattro passaggi.

Esaminiamo un esempio end-to-end di esecuzione di un modello con un operatore personalizzato tf.atan (denominato Atan , fare riferimento a #create_a_tensorflow_model) che è supportato in TensorFlow, ma non supportato in TensorFlow Lite.

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

Esempio: operatore Atan personalizzato

Esaminiamo un esempio di supporto di un operatore TensorFlow che TensorFlow Lite non dispone. Supponiamo di utilizzare l'operatore Atan e di costruire un modello molto semplice per una funzione y = atan(x + offset) , dove offset è addestrabile.

Crea un modello TensorFlow

Il seguente frammento di codice addestra un semplice modello TensorFlow. Questo modello contiene solo un operatore personalizzato denominato Atan , che è una funzione y = atan(x + offset) , dove offset è addestrabile.

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 del 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 i seguenti:

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

Riceverai comunque l'errore:

Encountered unresolved custom op: Atan.

Creare e registrare l'operatore.

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

Gli operatori personalizzati di TensorFlow Lite vengono definiti utilizzando una semplice API puro C costituita da un tipo opaco ( TfLiteRegistrationExternal ) e funzioni correlate.

TfLiteRegistrationExternal è un tipo opaco:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal memorizza l'identità e l'implementazione dell'operatore. (Si noti che l'operatore è distinto dai suoi operandi, che sono memorizzati nei nodi del grafico TF Lite per i nodi che chiamano l'operatore.)

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

L'identità dell'operatore viene impostata tramite i parametri della funzione di costruzione 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 venga valutato con successo, 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 nomi delle funzioni (o i prefissi degli spazi dei nomi, per C++) nell'implementazione op non devono corrispondere ai nomi delle funzioni nel frammento di codice precedente, poiché l'API delle operazioni personalizzate TF Lite utilizzerà solo i loro indirizzi. Ti consigliamo infatti di dichiararli in uno spazio dei nomi anonimo o come funzioni statiche.

Ma è una buona idea includere il nome dell'operatore come spazio dei nomi o prefisso sui 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é si tratta di un'API C, questi "metodi" vengono implementati come puntatori a funzioni C nel tipo TfLiteRegistrationExternal , che vengono impostati passando gli indirizzi delle funzioni di implementazione alle funzioni setter corrispondenti 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));

Fare riferimento a common.h per i dettagli su TfLiteContext e TfLiteNode . TfLiteContext fornisce funzionalità di segnalazione degli errori e accesso a 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 ciascun nodo del grafico. Un dato 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 associa i nomi dei parametri ai loro valori. Il buffer è vuoto per le operazioni integrate perché l'interprete ha già analizzato i parametri op. Le implementazioni del kernel che richiedono lo stato dovrebbero inizializzarlo qui e trasferire la proprietà al chiamante. Per ogni chiamata Init() , ci sarà una chiamata corrispondente a Free() , consentendo alle implementazioni di smaltire il buffer che potrebbero aver allocato in Init() .

Ogni volta che i tensori di input vengono ridimensionati, l'interprete passerà attraverso il grafico notificando le implementazioni del cambiamento. Ciò dà loro la possibilità di ridimensionare il buffer interno, verificare la validità delle forme e dei tipi di input e ricalcolare le forme di output. Tutto ciò viene eseguito tramite il metodo Prepare() e le implementazioni possono accedere al proprio stato utilizzando TfLiteOpaqueNodeGetUserData(node) .

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

Le operazioni personalizzate possono essere implementate definendo tali funzioni "metodo" e quindi definendo una funzione che restituisce un'istanza di TfLiteRegistrationExternal costruita chiamando TfLiteRegistrationExternalCreate e quindi i relativi metodi setter:

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 funzione MyCustomOpRegistration (vedi dettagli di seguito). Mentre il BuiltinOpResolver standard (disponibile dal target :builtin_ops ) si occupa della registrazione dei builtin, le operazioni personalizzate dovranno essere raccolte in librerie personalizzate separate.

Definizione del kernel nel runtime TensorFlow Lite

Tutto quello che dobbiamo fare per utilizzare l'operazione in TensorFlow Lite è definire due funzioni ( Prepare ed Eval ) e una terza per costruire un 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;
}
      

Quando inizializzi OpResolver , aggiungi l'operazione personalizzata nel risolutore (vedi sotto per un esempio). Ciò registrerà l'operatore 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 definite per l'operazione personalizzata. Se utilizzassi le funzioni AtanInit e AtanFree rispettivamente per inizializzare le variabili utilizzate nell'operazione e per liberare spazio, verrebbero aggiunte ai primi due argomenti di TfLiteRegistration ; tali argomenti sono impostati su nullptr in questo esempio.

Registrare l'operatore con la libreria del kernel

Ora dobbiamo registrare l'operatore con la libreria del kernel. Questo viene fatto con un OpResolver . Dietro le quinte, l'interprete caricherà una libreria di kernel a cui verrà assegnato l'esecuzione di ciascuno degli operatori del modello. Anche se la libreria predefinita contiene solo kernel integrati, è possibile sostituirla/aumentarla con una libreria personalizzata per gli operatori.

La classe OpResolver , che traduce i codici e i nomi degli operatori in codice vero e proprio, è definita in questo modo:

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

Si noti che per compatibilità con le versioni precedenti, questa classe utilizza il vecchio tipo concreto TfLiteRegistration anziché il tipo opaco TfLiteRegistrationExternal , ma la struttura 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'utilizzo di BuiltinOpResolver e la scrittura:

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

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

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

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

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

Si noti che un processo simile a quello sopra può essere seguito per supportare un insieme di operazioni invece di un singolo operatore. Aggiungi semplicemente tutti gli operatori AddCustom di cui hai bisogno. Inoltre, MutableOpResolver consente anche di sovrascrivere le implementazioni dei built-in utilizzando AddBuiltin .

Testa e profila il tuo operatore

Per profilare la tua operazione con lo strumento benchmark TensorFlow Lite, puoi utilizzare lo strumento modello 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

Migliori pratiche

  1. Ottimizzare con cautela le allocazioni e deallocazioni di memoria. L'allocazione della memoria in Prepare è più efficiente rispetto a Invoke e l'allocazione della memoria prima di un ciclo è migliore rispetto a ogni iterazione. Utilizza i dati dei tensori temporanei invece di mallozzarti (vedi punto 2). Usa puntatori/riferimenti invece di copiare il più possibile.

  2. Se una struttura dati persiste durante l'intera operazione, consigliamo di preallocare la memoria utilizzando tensori temporanei. Potrebbe essere necessario utilizzare una struttura OpData per fare riferimento agli indici tensoriali 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 non costa troppa memoria sprecata, preferisci utilizzare un array statico di dimensione fissa (o un std::vector pre-allocato in Resize ) piuttosto che utilizzare un std::vector allocato dinamicamente ogni iterazione dell'esecuzione.

  4. Evitare di creare un'istanza di modelli di contenitore della libreria standard che non esistono già, poiché influiscono sulla dimensione binaria. Ad esempio, se nella tua operazione è necessario un std::map che non esiste in altri kernel, l'utilizzo di un std::vector con mappatura di indicizzazione diretta potrebbe funzionare mantenendo piccola la dimensione binaria. Scopri quali altri kernel utilizzano per ottenere informazioni (o chiedere).

  5. Controlla il puntatore alla memoria restituita da malloc . Se questo puntatore è nullptr , nessuna operazione deve essere eseguita utilizzando quel puntatore. Se esegui malloc in una funzione e si verifica un errore all'uscita, dealloca la memoria prima di uscire.

  6. Utilizzare TF_LITE_OPAQUE_ENSURE(context, condition) per verificare una condizione specifica. Il tuo codice non deve lasciare la memoria in sospeso quando viene utilizzato TF_LITE_OPAQUE_ENSURE , ovvero queste macro dovrebbero essere utilizzate prima che vengano allocate eventuali risorse che perderanno.