Operadores personalizados

Como a biblioteca de operadores integrados do TensorFlow Lite aceita apenas um número limitado de operadores do TensorFlow, nem todos os modelos são conversíveis. Para mais detalhes, consulte compatibilidade do operador.

Para permitir a conversão, os usuários podem fornecer a própria implementação personalizada de um operador do TensorFlow não compatível no TensorFlow Lite, conhecido como operador personalizado. Se, em vez disso, você quiser combinar uma série de operadores do TensorFlow sem suporte (ou compatíveis) em um único operador personalizado e otimizado, consulte fusão de operadores.

O uso de operadores personalizados consiste em quatro etapas.

Vejamos um exemplo completo da execução de um modelo com um operador personalizado tf.atan (chamado de Atan, consulte Criar um modelo do TensorFlow), que é compatível com o TensorFlow, mas não compatível com o TensorFlow Lite.

O operador TensorFlow Text é um exemplo de operador personalizado. Consulte o tutorial Converter TF Text em TF Lite para ver um exemplo de código.

Exemplo: operador Atan personalizado

Vejamos um exemplo de suporte a um operador do TensorFlow que o TensorFlow Lite não tem. Suponha que estamos usando o operador Atan e que estamos criando um modelo muito simples para uma função y = atan(x + offset), em que offset é treinável.

Criar um modelo do TensorFlow

O snippet de código a seguir treina um modelo simples do TensorFlow. Esse modelo contém apenas um operador personalizado chamado Atan, que é uma função y = atan(x + offset), em que offset é treinável.

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

Neste ponto, se você tentar gerar um modelo do TensorFlow Lite com as sinalizações do conversor padrão, você receberá a seguinte mensagem de erro:

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

Converter para um modelo do TensorFlow Lite

Crie um modelo do TensorFlow Lite com operadores personalizados, definindo o atributo de conversor allow_custom_ops, conforme mostrado abaixo:

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

Nesse ponto, se você o executar com o intérprete padrão usando comandos como os seguintes:

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

Você ainda vai receber o erro:

Encountered unresolved custom op: Atan.

Crie e registre o operador.

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

Os operadores personalizados do TensorFlow Lite são definidos usando uma API pura-C simples que consiste em um tipo opaco (TfLiteRegistrationExternal) e funções relacionadas.

TfLiteRegistrationExternal é um tipo opaco:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal armazena a identidade e a implementação do operador. O operador é diferente dos operandos dele, que são armazenados nos nós de gráfico do TF Lite para nós que chamam o operador.

Instâncias desse tipo são construídas com chamadas para TfLiteRegistrationExternalCreate e podem ser destruídas chamando TfLiteRegistrationExternalDelete.

A identidade do operador é definida por meio dos parâmetros para a função construtora 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.
);

A implementação do operador pode definir "métodos" com as seguintes assinaturas. Todos esses métodos são opcionais, mas, para que um operador seja avaliado, a implementação dele precisa definir e definir, usando as funções setter, pelo menos os 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);

Os names das funções (ou prefixos de namespace, para C++) na implementação da operação não precisam corresponder aos nomes das funções no snippet de código acima, já que a API de operações personalizadas do TF Lite usará apenas os endereços delas. Recomendamos que você os declare em um namespace anônimo ou como funções estáticas.

Mas é uma boa ideia incluir o nome do operador como um namespace ou prefixo nesses nomes de função:

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 se trata de uma API C, esses "métodos" são implementados como ponteiros de função C no tipo TfLiteRegistrationExternal, que são definidos transmitindo os endereços das funções de implementação para as funções setter correspondentes 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));

Consulte common.h para ver detalhes sobre TfLiteContext e TfLiteNode. TfLiteContext fornece recursos de Error Reporting e acesso a objetos globais, incluindo todos os tensores. TfLiteNode permite que as implementações do operador acessem as entradas e saídas delas.

Quando o intérprete carrega um modelo, ele chama o método Init() uma vez para cada nó no gráfico. Um determinado Init() será chamado mais de uma vez se a operação for usada várias vezes no gráfico. Para operações personalizadas, um buffer de configuração será fornecido, contendo um flexbuffer que mapeia nomes de parâmetros para seus valores. O buffer está vazio para operações integradas porque o intérprete já analisou os parâmetros operacionais. As implementações do kernel que exigem estado precisam inicializá-lo aqui e transferir a propriedade para o autor da chamada. Para cada chamada Init(), haverá uma chamada correspondente para Free(), permitindo que as implementações descartem o buffer que podem ter alocado em Init().

Sempre que os tensores de entrada forem redimensionados, o intérprete passará pelo gráfico notificando as implementações da mudança. Isso permite que eles redimensionem o buffer interno, verifiquem a validade das formas e tipos de entrada e recalculem formas de saída. Tudo isso é feito pelo método Prepare(), e as implementações podem acessar o estado usando TfLiteOpaqueNodeGetUserData(node).

Por fim, toda vez que a inferência é executada, o intérprete atravessa o gráfico chamando o método Invoke(). Nesse caso, o estado também está disponível como TfLiteOpaqueNodeGetUserData(node).

Para implementar operações personalizadas, defina essas funções de "método" e, em seguida, defina uma função que retorne uma instância de TfLiteRegistrationExternal criada chamando TfLiteRegistrationExternalCreate e os métodos setter 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;
}
      

Observe que o registro não é automático e é necessário fazer uma chamada explícita para sua função MyCustomOpRegistration (veja os detalhes abaixo). Enquanto o BuiltinOpResolver padrão (disponível no destino :builtin_ops) se encarrega do registro de built-ins, as operações personalizadas precisam ser coletadas em bibliotecas personalizadas separadas.

Como definir o kernel no ambiente de execução do TensorFlow Lite

Tudo o que precisamos fazer para usar a operação no TensorFlow Lite é definir duas funções (Prepare e Eval) e uma terceira para criar um 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;
}
      

Ao inicializar o OpResolver, adicione a operação personalizada ao resolvedor. Confira um exemplo abaixo. Isso registrará o operador no Tensorflow Lite para que o TensorFlow Lite possa usar a nova implementação. Os dois últimos argumentos em TfLiteRegistration correspondem às funções AtanPrepare e AtanEval que você definiu para a operação personalizada. Se você usou as funções AtanInit e AtanFree para inicializar variáveis usadas na operação e liberar espaço, respectivamente, eles serão adicionados aos dois primeiros argumentos de TfLiteRegistration. Esses argumentos são definidos como nullptr neste exemplo.

Registrar o operador na biblioteca do kernel

Agora precisamos registrar o operador com a biblioteca do kernel. Isso é feito com um OpResolver. Em segundo plano, o intérprete carrega uma biblioteca de kernels, que é atribuída para executar cada um dos operadores no modelo. Embora a biblioteca padrão contenha apenas kernels integrados, é possível substituí-la/aumentá-la por operadores de operação de biblioteca personalizados.

A classe OpResolver, que converte códigos e nomes de operadores em código real, é definida assim:

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

Observe que, para compatibilidade com versões anteriores, essa classe usa o tipo concreto mais antigo TfLiteRegistration em vez do tipo opaco TfLiteRegistrationExternal, mas a estrutura TfLiteRegistration contém um campo registration_external do tipo TfLiteRegistrationExternal*.

As classes MutableOpResolver e BuiltinOpResolver são derivadas 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.
};

O uso regular (sem operações personalizadas) exige que você use BuiltinOpResolver e grave:

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

Para adicionar a operação personalizada criada acima, use um MutableOpResolver e chame AddCustom (antes de transmitir o resolvedor para o InterpreterBuilder):

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

Se o conjunto de operações integradas for considerado muito grande, um novo OpResolver poderá ser gerado de código com base em um determinado subconjunto de operações, possivelmente apenas as contidas em um determinado modelo. Isso equivale ao registro seletivo do TensorFlow, e uma versão simples dele está disponível no diretório tools.

Caso queira definir os operadores personalizados em Java, é necessário criar sua própria camada JNI personalizada e compilar seu AAR neste código jni (link em inglês). Da mesma forma, para definir os operadores disponíveis em Python, coloque os registros no código do wrapper do Python (em inglês).

Um processo semelhante ao mostrado acima pode ser seguido para dar suporte a um conjunto de operações em vez de um único operador. Basta adicionar quantos operadores AddCustom forem necessários. Além disso, MutableOpResolver também permite substituir implementações de integrados usando AddBuiltin.

Testar e criar o perfil do operador

Para criar o perfil da operação com a ferramenta de comparação do TensorFlow Lite, use a ferramenta de modelo de comparação para o TensorFlow Lite. Para fins de teste, é possível tornar seu build local do TensorFlow Lite ciente da operação personalizada adicionando a chamada AddCustom apropriada (como mostrado acima) para register.cc.

Práticas recomendadas

  1. Otimize as alocações de memória e as desalocações com cuidado. Alocar memória em Prepare é mais eficiente do que em Invoke. Além disso, alocar memória antes de um loop é melhor do que em cada iteração. Use os dados de tensores temporários em vez de se limitar (consulte o item 2). Use ponteiros/referências em vez de copiar o máximo possível.

  2. Se uma estrutura de dados persistir durante toda a operação, recomendamos a pré-alocação da memória usando tensores temporários. Talvez seja necessário usar um struct OpData para fazer referência aos índices de tensor em outras funções. Veja o exemplo no kernel para convolução. Confira abaixo um exemplo de snippet de código.

    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 isso não gastar muito memória desperdiçada, prefira usar uma matriz de tamanho fixo estático (ou um std::vector pré-alocado em Resize) em vez de usar um std::vector alocado dinamicamente a cada iteração de execução.

  4. Evite instanciar modelos de contêiner de biblioteca padrão que ainda não existem, porque eles afetam o tamanho binário. Por exemplo, se você precisar de um std::map na operação que não existe em outros kernels, o uso de um std::vector com mapeamento de indexação direta pode funcionar, mantendo o tamanho binário pequeno. Veja o que outros kernels usam para ter informações (ou perguntar).

  5. Confira o ponteiro para a memória retornada por malloc. Se esse ponteiro for nullptr, nenhuma operação precisará ser executada usando esse ponteiro. Se você malloc em uma função e tiver uma saída de erro, desaloque a memória antes de sair.

  6. Use TF_LITE_OPAQUE_ENSURE(context, condition) para verificar se há uma condição específica. Seu código não pode deixar a memória suspensa quando TF_LITE_OPAQUE_ENSURE for usado, ou seja, essas macros precisam ser usadas antes de qualquer recurso com vazamento vazar.