Operatori personalizzati

Poiché la libreria di operatori integrata LiteRT supporta solo un numero di operatori TensorFlow, non tutti i modelli sono convertibili. Per maggiori dettagli, consulta la sezione sulla compatibilità con gli operatori.

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

L'utilizzo di operatori personalizzati prevede quattro passaggi.

Esaminiamo un esempio end-to-end dell'esecuzione di un modello con una l'operatore tf.atan (denominato come Atan, consulta Creare un modello TensorFlow), è supportato in TensorFlow, ma non è supportato in LiteRT.

L'operatore TensorFlow Text è un esempio di operatore personalizzato. Consulta le Esempio di codice del tutorial Converti testo TF in LiteRT.

Esempio: operatore Atan personalizzato

Vediamo un esempio di come supportare un operatore TensorFlow LiteRT non contiene. Supponiamo di usare l'operatore Atan e che stiamo creando un modello molto semplice per una funzione y = atan(x + offset), in cui Puoi addestrare offset.

Crea un modello TensorFlow

Il seguente snippet di codice addestra un modello TensorFlow semplice. Questo modello contiene 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 LiteRT con di conversione, verrà visualizzato il seguente messaggio di errore:

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

Converti in un modello LiteRT

Crea un modello LiteRT con operatori personalizzati impostando il convertitore attributo 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 la esegui con l'interprete predefinito utilizzando comandi come che segue:

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

Riceverai comunque 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 LiteRT vengono definiti utilizzando una semplice API Pure-C che è costituito da un tipo opaco (TfLiteRegistrationExternal) e 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, che sono memorizzati nodi del grafico LiteRT per i nodi che chiamano l'operatore.)

Le istanze di questo tipo vengono create con chiamate a TfLiteRegistrationExternalCreate e può essere eliminata chiamando TfLiteRegistrationExternalDelete.

L'identità dell'operatore viene impostata tramite i parametri della funzione 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 riesca valutata, l'implementazione dell'operatore deve definire e impostare (utilizzando il setter funzioni) 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 dello spazio dei nomi per C++) nell'implementazione dell'operazione non devono corrispondere ai nomi delle funzioni nello snippet di codice riportato sopra, poiché TF L'API Lite Custom Ops utilizzerà solo i relativi indirizzi. In effetti, ti consigliamo dichiararle in uno spazio dei nomi anonimo o come funzioni statiche.

Tuttavia, è consigliabile includere il nome dell'operatore come spazio dei nomi o prefisso questi nomi di 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" sono implementate come puntatori di funzioni C TfLiteRegistrationExternal, che vengono impostati passando gli indirizzi di le 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 common.h per informazioni dettagliate su TfLiteContext e TfLiteNode. TfLiteContext restituisce un errore strutture di reporting e accesso agli oggetti globali, inclusi tutti i tensori. TfLiteNode consente alle implementazioni degli operatori di accedere ai rispettivi input e output.

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

Ogni volta che i tensori di input vengono ridimensionati, l'interprete esamina la di notifica delle implementazioni della modifica. Questo offre loro la possibilità di ridimensionarne il buffer interno, verificare la validità delle forme e dei tipi di input e ricalcola le forme di output. È possibile farlo mediante il metodo Prepare(). le implementazioni possono accedere al loro stato TfLiteOpaqueNodeGetUserData(node).

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

Le operazioni personalizzate possono essere implementate definendo questo "metodo" funzioni e quindi definire una funzione che restituisce un'istanza di TfLiteRegistrationExternal creato chiamando TfLiteRegistrationExternalCreate e quindi il valore metodi di impostazione:

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 e richiede una chiamata esplicita al tuo È necessario creare la funzione MyCustomOpRegistration (vedi i dettagli di seguito). Mentre BuiltinOpResolver standard (disponibile dal target :builtin_ops) richiede della registrazione dei componenti integrati, le operazioni personalizzate dovranno essere librerie personalizzate separate.

definisci il kernel nel runtime LiteRT

Per usare l'operazione in LiteRT basta definire due funzioni (Prepare e Eval) e un terzo per creare 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<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;
}
      

Durante l'inizializzazione di OpResolver, aggiungi l'operazione personalizzata nel resolver (vedi di seguito per un esempio). L'operatore verrà registrato con LiteRT che LiteRT possa usare la nuova implementazione. Tieni presente che gli ultimi due argomenti in TfLiteRegistration corrispondono a AtanPrepare e AtanEval definite per l'operazione personalizzata. Se hai utilizzato AtanInit e AtanFree per inizializzare le variabili usate nell'operazione e per liberare spazio, rispettivamente, verrebbero aggiunti ai primi due argomenti di TfLiteRegistration; in questo esempio questi argomenti sono impostati su nullptr.

Registra l'operatore con la libreria kernel

Ora dobbiamo registrare l'operatore con la libreria kernel. Questo viene fatto con OpResolver. Dietro le quinte, l'interprete caricherà una raccolta di kernel assegnati per eseguire ciascuno degli operatori nel modello. Sebbene la libreria predefinita contenga solo kernel integrati, è possibile sostituirlo/aumentarlo con operatori operativi della libreria personalizzati.

La classe OpResolver, che traduce i codici e i nomi degli operatori in ha la seguente definizione:

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

Tieni presente che, per compatibilità con le versioni precedenti, questa classe utilizza il tipo di calcestruzzo 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 dell'BuiltinOpResolver e scrivi:

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

Per aggiungere l'operazione personalizzata creata in precedenza, puoi utilizzare un'MutableOpResolver, e chiama AddCustom (prima di passare il resolver al InterpreterBuilder):

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

Se il gruppo di operazioni integrate è ritenuto troppo grande, potrebbe essere eseguito un nuovo OpResolver generati in base a un determinato sottoinsieme di operazioni, possibilmente solo quelle in un determinato modello. Equivale alla registrazione selettiva di TensorFlow (una versione semplice è disponibile nella directory tools).

Se vuoi definire gli operatori personalizzati in Java, al momento devi crea il tuo livello JNI personalizzato e compila il tuo AAR in questo codice jni. Analogamente, per definire questi operatori disponibili in Python, puoi inserisci le tue registrazioni nel il codice wrapper Python.

Tieni presente che è possibile seguire una procedura simile a quella descritta sopra per supportare un insieme anziché un singolo operatore. È sufficiente aggiungere tutti gli operatori AddCustom secondo le tue esigenze. Inoltre, MutableOpResolver consente anche di eseguire l'override implementazioni integrate utilizzando AddBuiltin.

Testa e profila il tuo operatore

Per profilare la tua attività con lo strumento di benchmark LiteRT, puoi utilizzare strumento per i modelli di benchmark per LiteRT. A scopo di test, puoi rendere la tua build locale LiteRT a conoscenza della tua operazione personalizzata aggiungendo l'elemento AddCustom appropriato chiama (come mostrato sopra) a register.cc

Best practice

  1. Ottimizza con cautela le allocazioni e le disallocazioni della memoria. Allocazione della memoria in corso... in Prepare è più efficiente rispetto a Invoke e alloca la memoria prima di un ciclo è meglio che in ogni iterazione. Utilizza dati tensori temporanei non tirarsi su le corna (vedi punto 2). Utilizza cursori/riferimenti di copiare il più possibile.

  2. Se una struttura di dati viene mantenuta durante l'intera operazione, ti consigliamo di preallocando la memoria usando tensori temporanei. Potresti dover utilizzare un OpData per fare riferimento agli indici del tensore in altre funzioni. Consulta le esempio nel kernel per convoluzione. Di seguito è riportato un esempio di snippet di codice.

    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 usare una dimensione fissa statica (o un std::vector preallocato in Resize) anziché utilizzare un array Allocazione dinamica di std::vector a ogni iterazione di esecuzione.

  4. Evita di creare un'istanza per modelli di container della libreria standard che non lo sono ancora perché influiscono sulle dimensioni binarie. Ad esempio, se hai bisogno di un nella tua operazione std::map che non esiste in altri kernel, utilizzando un std::vector con mappatura di indicizzazione diretta potrebbe funzionare, conservando il valore le dimensioni binarie sono piccole. Scopri cosa usano gli altri kernel per ottenere insight (o chiedere).

  5. Controlla il puntatore alla memoria restituita da malloc. Se questo puntatore è nullptr, non deve essere eseguita alcuna operazione utilizzando il puntatore. Se malloc in una funzione e presentano un'uscita di errore, dealloca la memoria prima di .

  6. Utilizza TF_LITE_OPAQUE_ENSURE(context, condition) per controllare uno specifico . Il codice non deve lasciare inutilizzata la memoria quando TF_LITE_OPAQUE_ENSURE, ovvero queste macro devono essere usate prima allocati risorse che trapelano.