Operadores personalizados

Como a biblioteca do operador LiteRT integrado é compatível apenas com de operadores TensorFlow, nem todos os modelos podem ser convertidos. Para mais detalhes, Consulte a compatibilidade do operador.

Para permitir a conversão, os usuários podem fornecer sua própria implementação personalizada de um operador sem suporte do TensorFlow no LiteRT, conhecido como operador personalizado. Se, em vez disso, você quiser combinar uma série de modelos operadores do TensorFlow em um único operador personalizado otimizado combinado, consulte fusão de operadores.

O uso de operadores personalizados consiste em quatro etapas.

Vamos conferir um exemplo completo de como executar um modelo com uma operador tf.atan (chamado de Atan, consulte Criar um modelo do TensorFlow), que é compatível com o TensorFlow, mas não é compatível com o LiteRT.

O operador TensorFlow Text é um exemplo de operador personalizado. Consulte a Tutorial Converter texto do TF em LiteRT para conferir um exemplo de código.

Exemplo: operador Atan personalizado

Vamos conferir um exemplo de suporte a um operador do TensorFlow que A LiteRT não tem. Vamos supor 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 apenas contém um operador personalizado chamado Atan, que é uma função y = atan(x + offset), em que offset pode ser treinado.

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 LiteRT com o padrão conversores, 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 LiteRT

Criar um modelo LiteRT com operadores personalizados configurando o conversor atributo allow_custom_ops, como mostrado abaixo:

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

Neste ponto, se você a executar com o interpretador padrão usando comandos como da seguinte forma:

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 LiteRT são definidos usando uma API pure-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. (Observe que o operador é diferente de seus operandos, que são armazenados no nós do gráfico LiteRT 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 assinaturas a seguir. Todos esses métodos são opcionais, mas para que um operador seja bem-sucedido, avaliado, a implementação do operador precisa definir e definir (usando a função 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);

a função names (ou prefixos de namespace, para C++) na sua implementação de op; não precisam corresponder aos nomes das funções no snippet de código acima, já que o A API de operações personalizadas Lite usará apenas os endereços delas. De fato, recomendamos que você declará-las em um namespace anônimo ou como funções estáticas.

Mas é uma boa ideia incluir o nome do seu operador como um namespace ou prefixo esses 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 em C, esses "métodos" são implementados como ponteiros de função C o tipo TfLiteRegistrationExternal, que são definidos transmitindo os endereços dos suas 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 mais detalhes sobre TfLiteContext e TfLiteNode. TfLiteContext informa um erro instalações de geração de relatórios e acesso a objetos globais, incluindo todos os tensores. TfLiteNode permite que as implementações de operadores acessem as entradas e saídas.

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. A está vazio para operações incorporadas porque o intérprete já analisou o parâmetros op. As implementações do kernel que exigem estado precisam inicializá-lo aqui e transferir a propriedade a quem está ligando. Para cada chamada de Init(), haverá uma chamada correspondente para Free(), permitindo que as implementações descartem o que eles podem ter alocado em Init().

Sempre que os tensores de entrada forem redimensionados, o intérprete vai passar pela gráfico notificando as implementações da alteração. Isso dá a eles a chance de redimensionar o buffer interno deles, verificar a validade dos tipos e formas de entrada e recalcular as formas de saída. Tudo isso é feito usando o método Prepare(). as implementações podem acessar o estado usando TfLiteOpaqueNodeGetUserData(node).

Por fim, sempre que a inferência é executada, o intérprete passa pelo grafo chamando o método Invoke(). Aqui também o estado está disponível como TfLiteOpaqueNodeGetUserData(node).

As operações personalizadas podem ser implementadas definindo esses "métodos" e, em seguida, Definir uma função que retorna uma instância de TfLiteRegistrationExternal. construído chamando TfLiteRegistrationExternalCreate e o conjunto de dados métodos setter:

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

O registro não é automático, e é preciso fazer uma chamada explícita ao seu A função MyCustomOpRegistration precisa ser criada (veja os detalhes abaixo). Enquanto o O BuiltinOpResolver padrão (disponível do destino :builtin_ops) leva e cuida do registro de builtins, as operações personalizadas terão que ser coletadas bibliotecas personalizadas separadas.

Como definir o kernel no ambiente de execução LiteRT

Para usar a operação no LiteRT, basta definir duas funções (Prepare e Eval) e um terceiro 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<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;
}
      

Ao inicializar o OpResolver, adicione a operação personalizada ao resolvedor. Consulte abaixo para ver um exemplo). Isso registrará o operador na LiteRT para que para que o LiteRT possa usar a nova implementação. Os dois últimos tipos argumentos em TfLiteRegistration correspondem a AtanPrepare e AtanEval que você definiu para a operação personalizada. Se você usou AtanInit e AtanFree para inicializar variáveis usadas na operação e liberar espaço. respectivamente, seriam somados aos dois primeiros argumentos de TfLiteRegistration Esses argumentos são definidos como nullptr neste exemplo.

Registrar o operador com a 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 serão atribuídos para executar cada um dos operadores no modelo. Embora a biblioteca padrão contenha apenas kernels integrados, é possível substituí-la/aumentá-la com operadores de operação de biblioteca personalizada.

A classe OpResolver, que converte nomes e códigos de operadores em é definida da seguinte forma:

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, esta classe usa o tipo concreto mais antigo TfLiteRegistration em vez do tipo opaco TfLiteRegistrationExternal, mas o struct TfLiteRegistration contém um campo registration_external de digite TfLiteRegistrationExternal*.

As classes MutableOpResolver e BuiltinOpResolver são derivadas 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 o BuiltinOpResolver e escrever:

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

Para adicionar a operação personalizada criada acima, use uma MutableOpResolver. e chamar AddCustom (antes de passar 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 com base em um determinado subconjunto de operações, possivelmente apenas aquelas 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).

Se você quiser definir seus operadores personalizados em Java, atualmente precisa criar sua própria camada JNI personalizada e compilar suas próprias AARs neste código JNI. Da mesma forma, se quiser definir esses operadores disponíveis em Python, você pode faça suas inscrições no Código do wrapper do Python (em inglês).

É possível seguir um processo semelhante ao acima para oferecer suporte a um conjunto de operações em vez de um único operador. Basta adicionar o máximo de operadores AddCustom sempre que precisar. Além disso, MutableOpResolver também permite substituir implementações de builtins usando AddBuiltin.

Testar e criar o perfil da sua operadora

Para criar o perfil da sua operação com a ferramenta de comparação LiteRT, use o ferramenta de modelo de comparativo de mercado para LiteRT. Para fins de teste, é possível fazer o build local do A LiteRT reconhece sua operação personalizada adicionando o AddCustom apropriado chamar (como mostrado acima) para register.cc

Práticas recomendadas

  1. Otimize as alocações de memória e as desalocações com cuidado. Alocação de memória em Prepare é mais eficiente do que em Invoke, e a alocação de memória antes de uma repetição é melhor do que em cada iteração. Usar dados de tensores temporários em vez de se adaptar (veja o item 2). Em vez disso, use ponteiros/referências de copiar o máximo possível.

  2. Se uma estrutura de dados persistir durante toda a operação, recomendamos pré-alocar a memória usando tensores temporários. Talvez seja necessário usar um struct OpData para fazer referência aos índices de tensores em outras funções. Consulte a exemplo na 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 não gastar muito memória desperdiçada, prefira usar um tamanho fixo estático (ou uma std::vector pré-alocada em Resize) em vez de usar uma alocou std::vector dinamicamente para cada iteração de execução.

  4. Evite instanciar modelos de contêiner da biblioteca padrão que ainda não tenham porque afetam o tamanho do binário. Por exemplo, se você precisar std::map na operação que não existe em outros kernels, usando uma O std::vector com mapeamento de indexação direta pode funcionar enquanto mantém o tamanho binário pequeno. Veja o que outros kernels usam para obter informações (ou perguntar).

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

  6. Use TF_LITE_OPAQUE_ENSURE(context, condition) para verificar se há um condição. Seu código não deve deixar a memória esperando quando TF_LITE_OPAQUE_ENSURE é usado, ou seja, essas macros devem ser usadas antes recursos alocados que podem vazar.