Поскольку встроенная библиотека операторов LiteRT поддерживает только ограниченное количество операторов TensorFlow, не каждую модель можно преобразовать. Подробную информацию см. в разделе «Совместимость операторов» .
Чтобы разрешить преобразование, пользователи могут предоставить собственную реализацию неподдерживаемого оператора TensorFlow в LiteRT, известного как пользовательский оператор. Если вместо этого вы хотите объединить ряд неподдерживаемых (или поддерживаемых) операторов TensorFlow в один оптимизированный пользовательский оператор Fused, обратитесь к разделу Оператор Fusion .
Использование пользовательских операторов состоит из четырех шагов.
Создайте модель TensorFlow. Убедитесь, что сохраненная модель (или определение графика) относится к правильно названному оператору LiteRT.
Преобразование в модель LiteRT. Убедитесь, что вы установили правильный атрибут конвертера LiteRT, чтобы успешно преобразовать модель.
Создайте и зарегистрируйте оператора. Это сделано для того, чтобы среда выполнения LiteRT знала, как сопоставить ваш оператор и параметры в вашем графике с исполняемым кодом C/C++.
Протестируйте и профилируйте своего оператора. Если вы хотите протестировать только свой собственный оператор, лучше всего создать модель только с вашим собственным оператором и использовать программу тестовой модели .
Давайте рассмотрим сквозной пример запуска модели с пользовательским оператором tf.atan
(с именем Atan
, см. Создание модели TensorFlow. ), который поддерживается в TensorFlow, но не поддерживается в LiteRT.
Оператор TensorFlow Text — это пример пользовательского оператора. Пример кода см. в руководстве «Преобразование текста TF в LiteRT» .
Пример: пользовательский оператор Atan
Давайте рассмотрим пример поддержки оператора TensorFlow, которого нет в LiteRT. Предположим, мы используем оператор Atan
и строим очень простую модель для функции y = atan(x + offset)
, где offset
поддается обучению.
Создайте модель TensorFlow
Следующий фрагмент кода обучает простую модель TensorFlow. Эта модель содержит только специальный оператор Atan
, который представляет собой функцию y = atan(x + offset)
, где offset
можно обучать.
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
На этом этапе, если вы попытаетесь создать модель LiteRT с флагами конвертера по умолчанию, вы получите следующее сообщение об ошибке:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Преобразование в модель LiteRT
Создайте модель LiteRT с настраиваемыми операторами, установив атрибут allow_custom_ops
, как показано ниже:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
На этом этапе, если вы запустите его с помощью интерпретатора по умолчанию, используя такие команды:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Вы все равно получите сообщение об ошибке:
Encountered unresolved custom op: Atan.
Создайте и зарегистрируйте оператора.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Пользовательские операторы LiteRT определяются с использованием простого API на чистом языке C, который состоит из непрозрачного типа ( TfLiteRegistrationExternal
) и связанных функций.
TfLiteRegistrationExternal
— непрозрачный тип:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
хранит идентификатор и реализацию оператора. (Обратите внимание, что оператор отличается от своих операндов, которые хранятся в узлах графа LiteRT для узлов, вызывающих оператор.)
Экземпляры этого типа создаются с помощью вызовов TfLiteRegistrationExternalCreate
и могут быть уничтожены с помощью вызова TfLiteRegistrationExternalDelete
.
Идентичность оператора устанавливается через параметры функции-конструктора 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.
);
Реализация оператора может определять «методы» со следующими сигнатурами. Все эти методы являются необязательными, но для успешной оценки оператора реализация оператора должна определить и установить (с помощью функций установки), по крайней мере, методы Prepare
и 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);
Имена функций (или префиксы пространства имен для C++) в вашей реализации операции не обязательно должны совпадать с именами функций в приведенном выше фрагменте кода, поскольку API пользовательских операций TF Lite будет использовать только их адреса. Действительно, мы рекомендуем объявлять их в анонимном пространстве имен или как статические функции.
Но хорошей идеей будет включить имя вашего оператора в качестве пространства имен или префикса в именах этих функций:
С++
namespace my_namespace::my_custom_op { void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of Free, Prepare, and Invoke ... }
С
void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and // MyCustomOpInvoke.
Поскольку это C API, эти «методы» реализованы как указатели на функции C в типе TfLiteRegistrationExternal
, которые задаются путем передачи адресов ваших функций реализации соответствующим функциям установки 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));
Обратитесь к common.h
для получения подробной информации о TfLiteContext
и TfLiteNode
. TfLiteContext
предоставляет средства сообщения об ошибках и доступ к глобальным объектам, включая все тензоры. TfLiteNode
позволяет реализациям операторов получать доступ к их входам и выходам.
Когда интерпретатор загружает модель, он вызывает метод Init()
один раз для каждого узла графа. Данная Init()
будет вызываться более одного раза, если операция используется в графе несколько раз. Для пользовательских операций будет предоставлен буфер конфигурации, содержащий гибкий буфер, который сопоставляет имена параметров с их значениями. Буфер пуст для встроенных операций, поскольку интерпретатор уже проанализировал параметры операции. Реализации ядра, которым требуется состояние, должны инициализировать его здесь и передать право владения вызывающему объекту. Для каждого вызова Init()
будет соответствующий вызов Free()
, что позволит реализациям избавиться от буфера, который они могли бы выделить в Init()
.
Всякий раз, когда размеры входных тензоров изменяются, интерпретатор проходит по графику, уведомляя реализации об изменении. Это дает им возможность изменить размер внутреннего буфера, проверить правильность входных форм и типов и пересчитать выходные формы. Все это делается с помощью метода Prepare()
, и реализации могут получить доступ к своему состоянию с помощью TfLiteOpaqueNodeGetUserData(node)
.
Наконец, каждый раз, когда выполняется вывод, интерпретатор обходит граф, вызывая метод Invoke()
, и здесь также состояние доступно как TfLiteOpaqueNodeGetUserData(node)
.
Пользовательские операции можно реализовать, определив эти функции «метода», а затем определив функцию, которая возвращает экземпляр TfLiteRegistrationExternal
, созданный путем вызова TfLiteRegistrationExternalCreate
, а затем соответствующих методов установки:
С++
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
С
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; }
Обратите внимание, что регистрация не является автоматической, и необходимо выполнить явный вызов функции MyCustomOpRegistration
(подробности см. ниже). В то время как стандартный BuiltinOpResolver
(доступный из цели :builtin_ops
) заботится о регистрации встроенных функций, пользовательские операции придется собирать в отдельные пользовательские библиотеки.
Определение ядра в среде выполнения LiteRT
Все, что нам нужно сделать, чтобы использовать операцию в LiteRT, — это определить две функции ( Prepare
и Eval
), а третью — создать TfLiteRegistrationExternal
:
С++
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
С
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; }
При инициализации OpResolver
добавьте специальную операцию в преобразователь (см. пример ниже). Это зарегистрирует оператор в LiteRT, чтобы LiteRT мог использовать новую реализацию. Обратите внимание, что последние два аргумента в TfLiteRegistration
соответствуют функциям AtanPrepare
и AtanEval
, которые вы определили для пользовательской операции. Если вы использовали функции AtanInit
и AtanFree
для инициализации переменных, используемых в операции, и для освобождения места соответственно, то они будут добавлены к первым двум аргументам TfLiteRegistration
; в этом примере для этих аргументов установлено значение nullptr
.
Зарегистрируйте оператор в библиотеке ядра
Теперь нам нужно зарегистрировать оператор в библиотеке ядра. Это делается с помощью OpResolver
. За кулисами интерпретатор загрузит библиотеку ядер, которая будет назначена для выполнения каждого из операторов в модели. Хотя библиотека по умолчанию содержит только встроенные ядра, ее можно заменить/дополнить пользовательскими операторами операций библиотеки.
Класс OpResolver
, который преобразует коды и имена операторов в реальный код, определяется следующим образом:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Обратите внимание, что для обратной совместимости этот класс использует более старый конкретный тип TfLiteRegistration
а не непрозрачный тип TfLiteRegistrationExternal
, но структура TfLiteRegistration
содержит поле registration_external
типа TfLiteRegistrationExternal*
.
Классы MutableOpResolver
и BuiltinOpResolver
являются производными от 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.
};
Обычное использование (без пользовательских операций) требует, чтобы вы использовали BuiltinOpResolver
и написали:
tflite::ops::builtin::BuiltinOpResolver resolver;
Чтобы добавить созданную выше пользовательскую операцию, вы можете вместо этого использовать MutableOpResolver
и вызвать AddCustom
(прежде чем передать преобразователь в InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Если набор встроенных операций считается слишком большим, новый OpResolver
может быть сгенерирован кодом на основе заданного подмножества операций, возможно, только тех, которые содержатся в данной модели. Это эквивалент выборочной регистрации TensorFlow (простая версия доступна в каталоге tools
).
Если вы хотите определить свои собственные операторы в Java, вам в настоящее время необходимо создать свой собственный уровень JNI и скомпилировать свой собственный AAR в этом коде jni . Аналогично, если вы хотите определить эти операторы, доступные в Python, вы можете разместить свои регистрации в коде-оболочке Python .
Обратите внимание, что аналогичный вышеописанному процесс можно использовать для поддержки набора операций вместо одного оператора. Просто добавьте столько операторов AddCustom
, сколько вам нужно. Кроме того, MutableOpResolver
также позволяет переопределять реализации встроенных функций с помощью AddBuiltin
.
Протестируйте и профилируйте своего оператора
Чтобы профилировать свою операцию с помощью эталонного инструмента LiteRT, вы можете использовать инструмент эталонной модели для LiteRT. В целях тестирования вы можете сделать так, чтобы ваша локальная сборка LiteRT знала о вашей пользовательской операции, добавив соответствующий вызов AddCustom
(как показано выше) в файл Register.cc.
Лучшие практики
Осторожно оптимизируйте выделение и освобождение памяти. Выделение памяти в
Prepare
более эффективно, чем вInvoke
, а выделение памяти перед циклом лучше, чем на каждой итерации. Используйте данные временных тензоров, а не занимайтесь маллокированием самостоятельно (см. пункт 2). Используйте указатели/ссылки вместо копирования, насколько это возможно.Если структура данных будет сохраняться в течение всей операции, мы советуем предварительно выделить память с помощью временных тензоров. Возможно, вам придется использовать структуру OpData для ссылки на индексы тензора в других функциях. См. пример в ядре для свертки . Пример фрагмента кода приведен ниже.
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; }
Если это не требует слишком много потраченной памяти, предпочтите использовать статический массив фиксированного размера (или предварительно выделенный
std::vector
вResize
), а не использовать динамически выделяемыйstd::vector
каждой итерации выполнения.Избегайте создания экземпляров шаблонов контейнеров стандартной библиотеки, которые еще не существуют, поскольку они влияют на двоичный размер. Например, если вам нужен
std::map
в вашей операции, который не существует в других ядрах, использованиеstd::vector
с прямым сопоставлением индексации может работать, сохраняя при этом небольшой размер двоичного файла. Посмотрите, что используют другие ядра, чтобы получить представление (или спросить).Проверьте указатель на память, возвращаемый
malloc
. Если этот указатель имеет значениеnullptr
, с его использованием не следует выполнять никакие операции. Если выmalloc
в функции и получаете выход из-за ошибки, освободите память перед выходом.Используйте
TF_LITE_OPAQUE_ENSURE(context, condition)
для проверки определенного условия. Ваш код не должен оставлять память зависающей при использованииTF_LITE_OPAQUE_ENSURE
, т. е. эти макросы следует использовать до того, как будут выделены какие-либо ресурсы, которые могут привести к утечке.