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.
Criar um modelo do TensorFlow. Verifique se a seção Model (ou Graph Def) se refere ao operador LiteRT corretamente nomeado.
Converter para um modelo LiteRT. Certifique-se de definir o atributo correto do conversor de LiteRT para converter com sucesso o modelo.
Crie e registre o operador. Isso é para que o ambiente de execução LiteRT saiba como mapear seu operador e no gráfico para o código C/C++ executável.
Teste e crie o perfil da sua operadora. Se você desejar testar somente seu operador personalizado, é melhor criar um modelo com apenas seu operador personalizado e usar o benchmark_model neste programa.
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
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 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
Otimize as alocações de memória e as desalocações com cuidado. Alocação de memória em
Prepare
é mais eficiente do que emInvoke
, 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.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; }
Se não gastar muito memória desperdiçada, prefira usar um tamanho fixo estático (ou uma
std::vector
pré-alocada emResize
) em vez de usar uma alocoustd::vector
dinamicamente para cada iteração de execução.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 Ostd::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).Verifique o ponteiro para a memória retornada por
malloc
. Se este ponteironullptr
, 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.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 quandoTF_LITE_OPAQUE_ENSURE
é usado, ou seja, essas macros devem ser usadas antes recursos alocados que podem vazar.