Operadores personalizados

Como a biblioteca de operadores integrada do TensorFlow Lite oferece suporte apenas a um número limitado de operadores do TensorFlow, nem todo modelo é conversível. Para obter detalhes, consulte compatibilidade do operador .

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

O uso de operadores personalizados consiste em quatro etapas.

Vamos examinar um exemplo completo de execução de um modelo com um operador personalizado tf.atan (denominado Atan , consulte #create_a_tensorflow_model) 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 texto TF em TF Lite para obter um exemplo de código.

Exemplo: operador Atan personalizado

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

Crie um modelo do TensorFlow

O snippet de código a seguir treina um modelo simples do TensorFlow. Este modelo contém apenas um operador personalizado chamado Atan , que é uma função y = atan(x + offset) , onde 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 os sinalizadores de conversor padrão, 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 do 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()

Neste ponto, se você executá-lo com o interpretador padrão usando comandos como os seguintes:

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

Você ainda receberá o erro:

Encountered unresolved custom op: Atan.

Crie e cadastre a operadora.

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

Os operadores personalizados do TensorFlow Lite são definidos usando uma API C pura 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 implementação do operador. (Observe que o operador é diferente de seus operandos, que são armazenados nos nós gráficos 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 da 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 com êxito, a implementação do operador 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 nomes das funções (ou prefixos de namespace, para C++) em sua implementação operacional não precisam corresponder aos nomes das funções no trecho de código acima, pois a API de operações personalizadas do TF Lite usará apenas seus endereços. Na verdade, recomendamos que você os declare 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 nestes nomes de funções:

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 é uma API C, esses "métodos" são implementados como ponteiros de função C no tipo TfLiteRegistrationExternal , que são definidos passando os endereços de suas funções de implementação para as funções setter correspondentes TfLiteRegistrationExternalSet MethodName :

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

Consulte common.h para obter detalhes sobre TfLiteContext e TfLiteNode . TfLiteContext fornece recursos de relatório de erros e acesso a objetos globais, incluindo todos os tensores. TfLiteNode permite que implementações de operadores acessem suas entradas e saídas.

Quando o intérprete carrega um modelo, ele chama o método Init() uma vez para cada nó do 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 internas porque o intérprete já analisou os parâmetros operacionais. As implementações do kernel que exigem estado devem inicializá-lo aqui e transferir a propriedade para o chamador. Para cada chamada Init() , haverá uma chamada correspondente para Free() , permitindo que as implementações descartem o buffer que possam ter alocado em Init() .

Sempre que os tensores de entrada forem redimensionados, o interpretador percorrerá o gráfico notificando as implementações da mudança. Isso lhes dá a oportunidade de redimensionar seu buffer interno, verificar a validade dos formatos e tipos de entrada e recalcular os formatos de saída. Tudo isso é feito através do método Prepare() , e as implementações podem acessar seu estado usando TfLiteOpaqueNodeGetUserData(node) .

Finalmente, cada vez que a inferência é executada, o interpretador percorre o gráfico chamando o método Invoke() , e aqui também o estado está disponível como TfLiteOpaqueNodeGetUserData(node) .

As operações personalizadas podem ser implementadas definindo essas funções de "método" e, em seguida, definindo uma função que retorna uma instância de TfLiteRegistrationExternal construída chamando TfLiteRegistrationExternalCreate e, em seguida, 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 uma chamada explícita para sua função MyCustomOpRegistration deve ser feita (veja detalhes abaixo). Embora o BuiltinOpResolver padrão (disponível no destino :builtin_ops ) cuide do registro de componentes integrados, as operações personalizadas terão que ser coletadas em bibliotecas personalizadas separadas.

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 construir 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 (veja um exemplo abaixo). Isso registrará o operador no Tensorflow Lite para que o TensorFlow Lite possa usar a nova implementação. Observe que os dois últimos argumentos em TfLiteRegistration correspondem às funções AtanPrepare e AtanEval que você definiu para a operação personalizada. Se você usasse as funções AtanInit e AtanFree para inicializar variáveis ​​usadas na operação e para liberar espaço, respectivamente, elas seriam adicionadas aos dois primeiros argumentos de TfLiteRegistration ; esses argumentos são definidos como nullptr neste exemplo.

Registre o operador na biblioteca do kernel

Agora precisamos registrar o operador na biblioteca do kernel. Isso é feito com um OpResolver . Nos bastidores, o interpretador carregará uma biblioteca de kernels que será atribuída para executar cada um dos operadores do modelo. Embora a biblioteca padrão contenha apenas kernels integrados, é possível substituí-la/aumentá-la por operadores operacionais de biblioteca personalizados.

A classe OpResolver , que traduz 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, esta 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) requer que você use o BuiltinOpResolver e escreva:

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

Para adicionar a operação personalizada criada acima, você pode usar MutableOpResolver e chamar AddCustom (antes de passar o resolvedor para 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 por código 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 precisará construir sua própria camada JNI personalizada e compilar seu próprio AAR neste código jni . Da mesma forma, se você deseja definir esses operadores disponíveis em Python você pode colocar seus registros no código wrapper do Python .

Observe que um processo semelhante ao acima pode ser seguido para suportar um conjunto de operações em vez de um único operador. Basta adicionar quantos operadores AddCustom você precisar. Além disso, MutableOpResolver também permite substituir implementações de componentes internos usando AddBuiltin .

Teste e crie o perfil da sua operadora

Para criar o perfil de sua operação com a ferramenta de benchmark do TensorFlow Lite, você pode usar a ferramenta de modelo de benchmark do TensorFlow Lite. Para fins de teste, você pode tornar sua versão local do TensorFlow Lite ciente de sua operação personalizada adicionando a chamada AddCustom apropriada (como mostrado acima) a Register.cc

Melhores Práticas

  1. Otimize as alocações e desalocações de memória com cautela. Alocar memória em Prepare é mais eficiente do que em Invoke e alocar memória antes de um loop é melhor do que em cada iteração. Use dados de tensores temporários em vez de se prejudicar (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, aconselhamos pré-alocar a memória usando tensores temporários. Pode ser necessário usar uma estrutura OpData para fazer referência aos índices tensores em outras funções. Veja o exemplo no kernel para convolução . Um exemplo de trecho de código está abaixo.

    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 custar muita memória desperdiçada, prefira usar uma matriz estática de tamanho fixo (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, pois eles afetam o tamanho binário. Por exemplo, se você precisar de um std::map em sua operação que não existe em outros kernels, usar um 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 insights (ou pergunte).

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

  6. Use TF_LITE_OPAQUE_ENSURE(context, condition) para verificar uma condição específica. Seu código não deve deixar a memória travada quando TF_LITE_OPAQUE_ENSURE for usado, ou seja, essas macros devem ser usadas antes de serem alocados quaisquer recursos que possam vazar.