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 detalles, consulta la compatibilidad con operadores.

Para permitir la conversión, los usuarios pueden proporcionar su propia implementación personalizada de un operador de TensorFlow no compatible en TensorFlow Lite, conocido como operador personalizado. En cambio, si deseas combinar una serie de operadores de TensorFlow no compatibles (o admitidos) en un solo operador personalizado optimizado y combinado, consulta la sección sobre fusión de operadores.

El uso de operadores personalizados consta de cuatro pasos.

Veamos un ejemplo completo de cómo ejecutar un modelo con un operador personalizado tf.atan (llamado Atan; consulta Crea un modelo de TensorFlow) que es compatible con TensorFlow, pero no con TensorFlow Lite.

El operador de texto de TensorFlow es un ejemplo de un operador personalizado. Consulta el instructivo Convierte 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 usamos el operador Atan y que estamos compilando un modelo muy simple para una función y = atan(x + offset) en la que offset se puede entrenar.

Crea un modelo de TensorFlow

En el siguiente fragmento de código, se entrena un modelo simple de TensorFlow. Este modelo solo contiene un operador personalizado llamado Atan, que es una función y = atan(x + offset), en la que offset es entrenable.

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 intentas generar un modelo de TensorFlow Lite con las marcas predeterminadas del conversor, recibirás el siguiente mensaje de error:

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

Convierte a un modelo de TensorFlow Lite

Para crear un modelo de TensorFlow Lite con operadores personalizados, configura el atributo de conversor 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 ejecutas con el intérprete predeterminado y usas comandos como los siguientes:

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

Seguirás recibiendo el siguiente error:

Encountered unresolved custom op: Atan.

Crea y registra el operador.

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

Los operadores personalizados de TensorFlow Lite se definen mediante una API simple de tipo puro C que consiste en 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. (ten en cuenta que el operador es distinto de sus operandos, que se almacenan en los nodos del gráfico de 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 a TfLiteRegistrationExternalDelete.

La identidad del operador se establece a través de los parámetros de la función de constructor 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 correctamente, la implementación del operador debe definir y establecer (con las funciones set) al menos los métodos Prepare y 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 names de función (o prefijos de espacio de nombres, para C++) en tu 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, te recomendamos que las declares en un espacio de nombres anónimo o como funciones estáticas.

Sin embargo, es una buena idea incluir el nombre de tu operador como un espacio de nombres o un 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.
      

Como esta es una API de C, estos "métodos" se implementan como punteros de función C en el tipo TfLiteRegistrationExternal. Para configurarlos, pasa las direcciones de tus funciones de implementación a las funciones set correspondientes 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 para obtener detalles sobre TfLiteContext y TfLiteNode. TfLiteContext proporciona servicios de informes de errores y acceso a objetos globales, incluidos todos los tensores. TfLiteNode permite que las implementaciones de operadores accedan a sus entradas y salidas.

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

Cada vez que se cambie el tamaño de los tensores de entrada, el intérprete recorrerá el gráfico que notifica 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 volver a calcular las formas de salida. Todo esto se realiza a través del método Prepare(), y las implementaciones pueden acceder a su estado mediante TfLiteOpaqueNodeGetUserData(node).

Por último, cada vez que se ejecuta la inferencia, el intérprete recorre el grafo que llama al método Invoke() y, en este caso, el estado también está disponible como TfLiteOpaqueNodeGetUserData(node).

Para implementar ops personalizadas, define esas funciones de "método" y, luego, una función que muestre una instancia de TfLiteRegistrationExternal construida mediante una llamada a TfLiteRegistrationExternalCreate y a los métodos set 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;
}
      

Ten en cuenta que el registro no es automático y se debe realizar una llamada explícita a la función MyCustomOpRegistration (consulta los detalles a continuación). Si bien el BuiltinOpResolver estándar (disponible desde el destino :builtin_ops) se encarga del registro de los elementos integrados, las ops personalizadas deberán recopilarse en bibliotecas personalizadas separadas.

Cómo definir el kernel en el entorno de ejecución de TensorFlow Lite

Lo único que debemos hacer para usar la op 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;
}
      

Cuando inicialices el OpResolver, agrega la op personalizada al agente de resolución (consulta a continuación para ver un ejemplo). Esto registrará el operador en Tensorflow Lite para que este pueda usar la implementación nueva. Ten en cuenta que los últimos dos argumentos de TfLiteRegistration corresponden a las funciones AtanPrepare y AtanEval que definiste para la operación personalizada. Si usaste las funciones AtanInit y AtanFree para inicializar las variables que se usan en la operación y liberar espacio, respectivamente, se agregarán a los dos primeros argumentos de TfLiteRegistration. En este ejemplo, esos argumentos se establecen en nullptr.

Registra el operador con la biblioteca de kernel

Ahora debemos registrar el operador en la biblioteca del kernel. Esto se hace con un OpResolver. En segundo plano, el intérprete cargará una biblioteca de kernels que se asignará para ejecutar cada uno de los operadores del modelo. Si bien la biblioteca predeterminada solo contiene kernels integrados, es posible reemplazarla o aumentarla por operadores de operaciones de biblioteca personalizados.

La clase OpResolver, que traduce los códigos y nombres de operadores en un código real, se define de la siguiente manera:

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

Ten en cuenta que, para la retrocompatibilidad, esta clase usa 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 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 habitual (sin operaciones personalizadas) requiere que uses BuiltinOpResolver y escribas lo siguiente:

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

Para agregar la operación personalizada que creaste antes, puedes usar un MutableOpResolver y llamar a AddCustom (antes de pasar el agente de resolución a InterpreterBuilder):

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

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

Si deseas definir tus operadores personalizados en Java, actualmente necesitarás compilar tu propia capa de JNI personalizada y compilar tu propio AAR en este código Jni. Del mismo modo, si deseas definir estos operadores disponibles en Python, puedes colocar tus registros en el código del wrapper de Python.

Ten en cuenta que se puede seguir un proceso similar al anterior para admitir un conjunto de operaciones en lugar de un solo operador. Solo agrega tantos operadores AddCustom como necesites. Además, MutableOpResolver también te permite anular implementaciones de elementos integrados mediante AddBuiltin.

Prueba y perfila tu operador

Para generar un perfil de tu operación con la herramienta de comparativas de TensorFlow Lite, puedes usar la herramienta de modelos de comparativas para TensorFlow Lite. Para realizar pruebas, puedes hacer que tu compilación local de TensorFlow Lite esté al tanto de tu operación personalizada. Para ello, agrega la llamada AddCustom adecuada (como se muestra más arriba) a register.cc

Prácticas recomendadas

  1. Optimiza las asignaciones y desasignaciones de memoria con precaución. La asignación de memoria en Prepare es más eficiente que en Invoke y la asignación de memoria antes de un bucle es mejor que en cada iteración. Usa datos de tensores temporales en lugar de Mallocarte (consulta el elemento 2). Usa punteros o referencias en lugar de copiar tanto como sea posible.

  2. Si una estructura de datos persistirá durante toda la operación, te recomendamos asignar previamente la memoria con tensores temporales. Es posible que debas usar una estructura OpData para hacer referencia a los índices de tensor en otras funciones. Consulta el ejemplo en el kernel para 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, es preferible usar un array de tamaño fijo estático (o un std::vector preasignado en Resize) en lugar de usar un std::vector asignado de forma dinámica en cada iteración de ejecución.

  4. Evita crear instancias de plantillas de contenedor de biblioteca estándar que aún no existan, ya que afectan el tamaño del objeto binario. Por ejemplo, si necesitas en tu operación una std::map que no existe en otros kernels, el uso de un std::vector con asignación de indexación directa podría funcionar mientras se mantiene pequeño el tamaño del objeto binario. Mira qué usan otros kernels para obtener información (o preguntar).

  5. Verifica el puntero a la memoria que muestra malloc. Si este puntero es nullptr, no se deben realizar operaciones con ese puntero. Si malloc en una función y tienes un error de salida, anula la asignación de memoria antes de salir.

  6. Usa TF_LITE_OPAQUE_ENSURE(context, condition) para verificar una condición específica. Tu código no debe dejar memoria en espera cuando se usa TF_LITE_OPAQUE_ENSURE, es decir, estas macros deben usarse antes de que se asigne cualquier recurso que tenga fugas.