Operadores personalizados

Dado que la biblioteca de operadores integrada de TensorFlow Lite solo admite una cantidad limitada de operadores de TensorFlow, no todos los modelos son convertibles. Para obtener más información, consulte compatibilidad de operadores .

Para permitir la conversión, los usuarios pueden proporcionar su propia implementación personalizada de un operador TensorFlow no compatible en TensorFlow Lite, conocido como operador personalizado. Si, en cambio, desea combinar una serie de operadores TensorFlow no compatibles (o compatibles) en un único operador personalizado optimizado fusionado, consulte Fusión de operadores .

El uso de operadores personalizados consta de cuatro pasos.

Veamos un ejemplo completo de ejecución de un modelo con un operador personalizado tf.atan (llamado Atan , consulte #create_a_tensorflow_model) que es compatible con TensorFlow, pero no es compatible con TensorFlow Lite.

El operador TensorFlow Text es un ejemplo de operador personalizado. Consulte el tutorial Convertir TF Text a TF Lite para ver un ejemplo de código.

Ejemplo: operador Atan personalizado

Veamos un ejemplo de compatibilidad con un operador de TensorFlow que TensorFlow Lite no tiene. Supongamos que estamos usando el operador Atan y que estamos construyendo un modelo muy simple para una función y = atan(x + offset) , donde offset se puede entrenar.

Crear un modelo de TensorFlow

El siguiente fragmento de código entrena un modelo simple de TensorFlow. Este modelo solo contiene un operador personalizado llamado Atan , que es una función y = atan(x + offset) , donde offset se puede entrenar.

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

En este punto, si intenta generar un modelo de TensorFlow Lite con los indicadores de conversión predeterminados, recibirá el siguiente mensaje de error:

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

Convertir a un modelo TensorFlow Lite

Cree un modelo de TensorFlow Lite con operadores personalizados configurando el atributo del convertidor allow_custom_ops como se muestra a continuación:

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

En este punto, si lo ejecuta con el intérprete predeterminado usando comandos como los siguientes:

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

Seguirás recibiendo el error:

Encountered unresolved custom op: Atan.

Crear y registrar el operador.

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

Los operadores personalizados de TensorFlow Lite se definen mediante una API simple de C puro que consta de un tipo opaco ( TfLiteRegistrationExternal ) y funciones relacionadas.

TfLiteRegistrationExternal es un tipo opaco:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal almacena la identidad y la implementación del operador. (Tenga en cuenta que el operador es distinto de sus operandos, que se almacenan en los nodos del gráfico TF Lite para los nodos que llaman al operador).

Las instancias de este tipo se construyen con llamadas a TfLiteRegistrationExternalCreate y se pueden destruir llamando TfLiteRegistrationExternalDelete .

La identidad del operador se establece mediante los parámetros de la función constructora 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.
);

La implementación del operador puede definir "métodos" con las siguientes firmas. Todos estos métodos son opcionales, pero para que un operador se evalúe exitosamente, la implementación del operador debe definir y configurar (usando las funciones de establecimiento) al menos los métodos 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);

Los nombres de las funciones (o prefijos de espacios de nombres, para C++) en su implementación de operaciones no tienen que coincidir con los nombres de las funciones en el fragmento de código anterior, ya que la API de operaciones personalizadas de TF Lite solo usará sus direcciones. De hecho, le recomendamos que los declare en un espacio de nombres anónimo o como funciones estáticas.

Pero es una buena idea incluir el nombre de su operador como espacio de nombres o prefijo en estos nombres de funciones:

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.
      

Dado que se trata de una API de C, estos "métodos" se implementan como punteros de funciones de C en el tipo TfLiteRegistrationExternal , que se configuran pasando las direcciones de sus funciones de implementación a las funciones de configuración correspondientes 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));

Consulte common.h para obtener detalles sobre TfLiteContext y TfLiteNode . TfLiteContext proporciona funciones de informe de errores y acceso a objetos globales, incluidos todos los tensores. TfLiteNode permite que las implementaciones del operador accedan a sus entradas y salidas.

Cuando el intérprete carga un modelo, llama al método Init() una vez para cada nodo del gráfico. Se llamará a un Init() determinado más de una vez si la operación se usa varias veces en el gráfico. Para operaciones personalizadas, se proporcionará un búfer de configuración que contiene un búfer flexible que asigna los nombres de los parámetros a sus valores. El búfer está vacío para las operaciones integradas porque el intérprete ya ha analizado los parámetros de la operación. Las implementaciones del kernel que requieren estado deben inicializarlo aquí y transferir la propiedad a la persona que llama. Para cada llamada Init() , habrá una llamada correspondiente a Free() , lo que permitirá a las implementaciones deshacerse del búfer que podrían haber asignado en Init() .

Cada vez que se cambia el tamaño de los tensores de entrada, el intérprete revisará el gráfico notificando las implementaciones del cambio. Esto les da la oportunidad de cambiar el tamaño de su búfer interno, verificar la validez de las formas y tipos de entrada y recalcular las formas de salida. Todo esto se hace a través del método Prepare() y las implementaciones pueden acceder a su estado usando TfLiteOpaqueNodeGetUserData(node) .

Finalmente, cada vez que se ejecuta la inferencia, el intérprete recorre el gráfico llamando al método Invoke() , y aquí también el estado está disponible como TfLiteOpaqueNodeGetUserData(node) .

Las operaciones personalizadas se pueden implementar definiendo esas funciones de "método" y luego definiendo una función que devuelva una instancia de TfLiteRegistrationExternal construida llamando TfLiteRegistrationExternalCreate y luego a los métodos de configuración relevantes:

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

Tenga en cuenta que el registro no es automático y se debe realizar una llamada explícita a su función MyCustomOpRegistration (consulte los detalles a continuación). Si bien el BuiltinOpResolver estándar (disponible en el destino :builtin_ops ) se encarga del registro de las funciones integradas, las operaciones personalizadas deberán recopilarse en bibliotecas personalizadas separadas.

Definición del kernel en el tiempo de ejecución de TensorFlow Lite

Todo lo que necesitamos hacer para usar la operación en TensorFlow Lite es definir dos funciones ( Prepare y Eval ) y una tercera para construir 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;
}
      

Al inicializar OpResolver , agregue la operación personalizada al solucionador (vea un ejemplo a continuación). Esto registrará al operador con Tensorflow Lite para que TensorFlow Lite pueda usar la nueva implementación. Tenga en cuenta que los dos últimos argumentos en TfLiteRegistration corresponden a las funciones AtanPrepare y AtanEval que definió para la operación personalizada. Si utilizó las funciones AtanInit y AtanFree para inicializar las variables utilizadas en la operación y liberar espacio, respectivamente, se agregarían a los dos primeros argumentos de TfLiteRegistration ; esos argumentos se establecen en nullptr en este ejemplo.

Registre el operador con la biblioteca del kernel.

Ahora necesitamos registrar el operador en la biblioteca del kernel. Esto se hace con un OpResolver . Detrás de escena, el intérprete cargará una biblioteca de núcleos que se asignarán para ejecutar cada uno de los operadores en el modelo. Si bien la biblioteca predeterminada solo contiene núcleos integrados, es posible reemplazarla/aumentarla con operadores operativos de biblioteca personalizados.

La clase OpResolver , que traduce códigos y nombres de operadores en código real, se define así:

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

Tenga en cuenta que para compatibilidad con versiones anteriores, esta clase utiliza el tipo concreto más antiguo TfLiteRegistration en lugar del tipo opaco TfLiteRegistrationExternal , pero la estructura TfLiteRegistration contiene un campo registration_external de tipo TfLiteRegistrationExternal* .

Las clases MutableOpResolver y BuiltinOpResolver se derivan de 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.
};

El uso regular (sin operaciones personalizadas) requiere que utilice BuiltinOpResolver y escriba:

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

Para agregar la operación personalizada creada anteriormente, puede usar MutableOpResolver y llamar a AddCustom (antes de pasar el solucionador a InterpreterBuilder ):

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

Si se considera que el conjunto de operaciones integradas es demasiado grande, se podría generar un nuevo código OpResolver basado en un subconjunto determinado de operaciones, posiblemente solo las contenidas en un modelo determinado. Este es el equivalente al registro selectivo de TensorFlow (y una versión simple está disponible en el directorio tools ).

Si desea definir sus operadores personalizados en Java, actualmente necesitará crear su propia capa JNI personalizada y compilar su propio AAR en este código jni . De manera similar, si desea definir estos operadores disponibles en Python, puede colocar sus registros en el código contenedor de Python .

Tenga en cuenta que se puede seguir un proceso similar al anterior para admitir un conjunto de operaciones en lugar de un solo operador. Simplemente agregue tantos operadores AddCustom como necesite. Además, MutableOpResolver también le permite anular implementaciones de funciones integradas mediante AddBuiltin .

Pruebe y perfile a su operador

Para perfilar su operación con la herramienta de referencia de TensorFlow Lite, puede utilizar la herramienta de modelo de referencia para TensorFlow Lite. Para fines de prueba, puede hacer que su compilación local de TensorFlow Lite tenga en cuenta su operación personalizada agregando la llamada AddCustom adecuada (como se muestra arriba) a Register.cc.

Mejores prácticas

  1. Optimice las asignaciones y desasignaciones de memoria con cautela. Asignar memoria en Prepare es más eficiente que en Invoke y asignar memoria antes de un bucle es mejor que en cada iteración. Utilice datos de tensores temporales en lugar de mallocarse usted mismo (consulte el punto 2). Utilice punteros/referencias en lugar de copiar tanto como sea posible.

  2. Si una estructura de datos persistirá durante toda la operación, recomendamos preasignar la memoria utilizando tensores temporales. Es posible que necesite utilizar una estructura OpData para hacer referencia a los índices tensoriales en otras funciones. Vea el ejemplo en el kernel para la convolución . A continuación se muestra un fragmento de código de muestra.

    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. Si no cuesta demasiada memoria desperdiciada, prefiera usar una matriz estática de tamaño fijo (o un std::vector preasignado en Resize ) en lugar de usar un std::vector asignado dinámicamente en cada iteración de ejecución.

  4. Evite crear instancias de plantillas de contenedores de bibliotecas estándar que aún no existan, porque afectan el tamaño binario. Por ejemplo, si necesita un std::map en su operación que no existe en otros núcleos, usar un std::vector con mapeo de indexación directa podría funcionar manteniendo el tamaño binario pequeño. Vea qué utilizan otros núcleos para obtener información (o preguntar).

  5. Verifique el puntero a la memoria devuelta por malloc . Si este puntero es nullptr , no se deben realizar operaciones con ese puntero. Si realiza malloc en una función y tiene un error al salir, desasigne la memoria antes de salir.

  6. Utilice TF_LITE_OPAQUE_ENSURE(context, condition) para verificar una condición específica. Su código no debe dejar la memoria bloqueada cuando se usa TF_LITE_OPAQUE_ENSURE , es decir, estas macros deben usarse antes de que se asignen recursos que se filtrarán.