Operadores personalizados

Dado que la biblioteca de operador integrada LiteRT solo admite un cantidad de operadores de TensorFlow, no todos los modelos son convertibles. Para obtener más información, consulta la compatibilidad con operadores.

Para permitir la conversión, los usuarios pueden proporcionar su propia implementación personalizada de un un operador de TensorFlow no compatible en LiteRT, conocido como operador personalizado. En cambio, si deseas combinar una serie de campañas operadores de TensorFlow en un solo operador personalizado fusionado y optimizado, consulta fusión de operadores.

El uso de operadores personalizados consta de cuatro pasos.

Analicemos un ejemplo integral de ejecución de un modelo con una configuración el operador tf.atan (llamado Atan, consulta Crea un modelo de TensorFlow), que es compatible con TensorFlow, pero no con LiteRT.

El operador de TensorFlow Text es un ejemplo de un operador personalizado. Consulta la Instructivo para convertir TF Text a LiteRT para ver un ejemplo de código.

Ejemplo: Operador Atan personalizado

Veamos un ejemplo de asistencia a un operador de TensorFlow que LiteRT no contiene. Supongamos que usamos el operador Atan y que crearemos un modelo muy sencillo para una función y = atan(x + offset), en la que Se puede entrenar offset.

Crea un modelo de TensorFlow

Con el siguiente fragmento de código, se entrena un modelo simple de TensorFlow. Este modelo acaba de contiene un operador personalizado llamado Atan, que es una función y = atan(x + offset), en la que 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 intentas generar un modelo LiteRT con la configuración de conversión, verás el siguiente mensaje de error:

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

Convierte a un modelo LiteRT

Configura el conversor para crear un modelo LiteRT con operadores personalizados el atributo 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 usando comandos como sigue:

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 LiteRT se definen con una API de C simple pura 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. (Ten en cuenta que el operador es distinto de sus operandos, que se almacenan en el Nodos de grafo LiteRT para nodos que llaman al operador).

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

La identidad del operador se establece mediante 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 funcione la implementación del operador debe definir y establecer (usando el método 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 nombres de las funciones (o prefijos de espacios de nombres, para C++) en la implementación de tu op no es necesario que coincidan con los nombres de las funciones en el fragmento de código anterior, ya que La API de operaciones personalizadas de Lite solo usará sus direcciones. De hecho, te recomendamos que declararlos en un espacio de nombres anónimo o como funciones estáticas

Pero es una buena idea incluir el nombre de tu 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.
      

Como esta es una API C, estos “métodos” se implementan como punteros de función C en el tipo TfLiteRegistrationExternal, que se establecen pasando las direcciones de tus funciones de implementación en 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 un error 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 en el gráfico. Se llamará a un Init() determinado más de una vez si la op es varias veces en el gráfico. Para las operaciones personalizadas, se almacenará un búfer de configuración que contiene un búfer flexible que asigna nombres de parámetros a sus valores. El el búfer está vacío para las operaciones integradas porque el intérprete ya analizó el op. Las implementaciones de kernel que requieren estado deben inicializarlo. y transferir la propiedad al emisor. Para cada llamada a Init(), habrá una llamada correspondiente a Free(), lo que permite que las implementaciones eliminen la búfer que podría haber asignado en Init().

Cuando se cambie el tamaño de los tensores de entrada, el intérprete pasará por el gráfico que notifica las implementaciones del cambio. Esto les da la oportunidad de cambiar el tamaño de su búfer interno, comprobar la validez de las formas y los tipos de entrada, y volver a calcular formas de salida. Todo esto se hace a través del método Prepare(). implementaciones pueden acceder a su estado con TfLiteOpaqueNodeGetUserData(node)

Por último, cada vez que se ejecuta la inferencia, el intérprete recorre el grafo llamando el método Invoke(), y aquí también el estado está disponible como TfLiteOpaqueNodeGetUserData(node)

Las ops personalizadas se pueden implementar definiendo esos “métodos” y, luego, definir una función que muestre una instancia de TfLiteRegistrationExternal construido llamando a TfLiteRegistrationExternalCreate y, luego, a la clase métodos set:

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 trata de una llamada explícita a tu MyCustomOpRegistration (consulta los detalles a continuación). Si bien el el BuiltinOpResolver estándar (disponible a partir del objetivo :builtin_ops) tarda del registro de los componentes integrados, las ops personalizadas deberán recopilarse en bibliotecas personalizadas separadas.

Cómo definir el kernel en el entorno de ejecución de LiteRT

Todo lo que tenemos que hacer para usar la op en LiteRT es definir dos funciones (Prepare y Eval) y un tercero 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<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;
}
      

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

Registra el operador con la biblioteca de kernel

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

La clase OpResolver, que traduce códigos y nombres de operadores a valores reales código, 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 antiguo TfLiteRegistration en lugar del tipo opaco TfLiteRegistrationExternal, pero el struct TfLiteRegistration contiene un campo registration_external de escribe 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 normal (sin operaciones personalizadas) requiere que uses BuiltinOpResolver y escribe lo siguiente:

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

Para agregar la op personalizada que creaste anteriormente, puedes usar un MutableOpResolver. y llama a AddCustom (antes de pasar el agente de resolución al 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 aplicar un OpResolver nuevo. generado por código a partir de un subconjunto de operaciones determinado, posiblemente solo las que en un modelo determinado. Esto equivale al registro selectivo de TensorFlow. (y hay una versión simple disponible en el directorio tools).

Si deseas definir tus operadores personalizados en Java, actualmente debes crea tu propia capa JNI personalizada y compila tus propias AAR en este código JNI. Del mismo modo, si quieres definir estos operadores disponibles en Python, puedes coloca tus registros en la Código de wrapper de Python.

Ten en cuenta que se puede seguir un proceso similar al anterior para admitir un conjunto de en lugar de un único operador. Solo agrega tantos operadores AddCustom a medida que lo necesites. MutableOpResolver también te permite anular implementaciones de componentes integrados con AddBuiltin.

Prueba tu operador y genera perfiles de él

Para generar perfiles de tus operaciones con la herramienta de comparativas LiteRT, puedes usar la herramienta de modelos comparativos para LiteRT. Para realizar pruebas, puedes hacer que tu compilación local de Agrega el AddCustom adecuado para conocer LiteRT de tus op personalizadas. llamada (como se muestra más arriba) a register.cc

Prácticas recomendadas

  1. Optimiza las asignaciones de memoria y las desasignaciones con precaución. 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. Usar datos de tensores temporales en lugar de mallocte a ti mismo (consulta el elemento 2). Usar punteros o referencias de copiar tanto como sea posible.

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

  4. Evita crear instancias de plantillas de contenedores de bibliotecas estándar que aún no tienen existen, porque afectan el tamaño del objeto binario. Por ejemplo, si necesitas una std::map en tu operación, que no existe en otros kernels, mediante un std::vector con la asignación de indexación directa podría funcionar sin perder la de tamaño binario pequeño. Consulta lo que otros kernels usan para obtener información (o pregunta).

  5. Comprueba 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 se produce un error, anula la asignación de memoria antes de salir.

  6. Usa TF_LITE_OPAQUE_ENSURE(context, condition) para buscar un nombre específico estado. Tu código no debe dejar en espera a la memoria cuando TF_LITE_OPAQUE_ENSURE; es decir, estas macros deben usarse antes se asignan recursos que se filtrarán.